微调(Fine-Tuning)

微调(Fine-Tuning)
可爱可倾微调(Fine-Tuning)
1 开源基座模型
大语言模型的训练分为两个阶段: (1)在海量文本语料上的无监督预训练,学习通用的语义表示和世界知识。 (2)在小规模数据上,进行指令微调和基于人类反馈的强化学习,更好地对齐最终任务和人类偏好。 几乎所有知识都是在预训练过程中学习到的,只需要有限的指令微调数据就可以生成高质量的回复。 因此,基座模型的性能是至关重要的,如果基座模型的性能不够好,指令微调和强化学习也难以取得很好的效果。
主流的开源大语言模型主要有三个:LLaMA、ChatGLM和BLOOM。
| 模型 | 模型结构 | 位置编码 | 激活函数 | layer norm |
|---|---|---|---|---|
| LLaMA | Casual decoder | RoPE | SwiGLU | Pre RMS Norm |
| ChatGLM-6B | Prefix decoder | RoPE | GeGLU | Post Deep Norm |
| Bloom | Casual decoder | ALiBi | GeLU | Pre Layer Norm |
模型对比
1) 词表扩充
LLaMA原模型的词表大小是32000,tokenizer主要是在英文语料上进行训练的,在中文上和多语种上效果比较差。
- LLaMA模型是在以英文为主的拉丁语系语料上进行训练的,训练语料不包含中文;
- 与tokenizer有关,词表规模小,可能将一个汉字切分为多个token,编码效率低,模型学习难度大。
扩展中文词表后,单个汉字倾向于被切分为1个token,避免了一个汉字被切分为多个token的问题,提升了中文编码效率。
- 在中文语料上使用Sentence Piece训练一个中文tokenizer。
- 将中文tokenizer与原始的 LLaMA tokenizer合并起来,通过组合二者的词汇表,最终获得一个合并的tokenizer,称为Chinese LLaMA tokenizer。
- 为了适应新的tokenizer,将transformer模型的embedding矩阵从\(V \times h\) 扩展到 \(V' \times h\),新加入的中文token附加到原始embedding矩阵的末尾,确保原始词表表的embedding矩阵不受影响。
- 在中文语料上进一步预训练,冻结和固定transformer的模型参数,只训练embedding矩阵,学习新加入中文token的词向量表示,同时最小化对原模型的干扰。
- 在指令微调阶段,可以放开全部模型参数进行训练。
2) 模型结构
主流模型都采用decoder-only结构,其中LLaMA和BLOOM采用了casual decoder,ChatGLM采用了prefix decoder。
图1:模型结构
- casual decoder:采用单向注意力掩码,以确保每个输入标记只能关注过去的标记和它本身。
- prefix decoder:对前缀标记执行双向注意力,并仅对生成的标记执行单向注意力。这样,与encoder-decoder类似,可以双向编码前缀序列并自回归的逐个预测输出标记,其中在编码和解码阶段共享相同的参数。
- attention mask不同,prefix LM的prefix部分的token互相能看到,causal LM严格遵守只有后面的token才能看到前面的token的规则。
- prefix decoder-only结构的ChatGLM存在一个劣势:训练效率低。causal decoder结构会在所有的token上计算损失,而prefix decoder只会在输出上计算损失,而不计算输入上的损失。在有相同数量的训练tokens的情况下,prefix decoder要比causal decoder的效果差,因为训练过程中实际用到的tokens数量要更少。
注:ChatGPT的成功已经证明了causal decoder结构的大语言模型可以获得非常好的few-shot和zero-shot生成能力,通过指令微调可以进一步激发模型的能力。至于prefix decoder结构的大语言模型能否获得相当的few-shot和zero-shot能力还缺少足够的验证。 (1) zero-shot:训练集中没有某个类别的样本,但是如果我们可以学到一个映射,这个映射好到即使在训练的时候没看到这个类,但是在遇到的时候依然能通过这个映射得到这个新类的特征。 (2) one-shot:模型在只见过一个训练样本的情况下,学习并完成新的任务。常见于图像识别领域。 (3) few-shot:模型在见过少量(通常是几个到几十个)训练样本的情况下,学习并完成新的任务。
Why decoder-only?
Encoder 在抽取序列中某一个词的特征时能够看到整个序列中所有的信息,即上文和下文同时看到。主要作用是从输入序列中抽取特征,形成一个表示序列的上下文向量; Decoder 中因为有 mask 机制的存在,使得它在编码某一个词的特征时只能看到自身和它之前的文本信息。主要作用是根据Encoder生成的上下文向量和之前已经生成的词,逐步生成目标序列中的每个词。
- 用过去研究的经验说话,decoder-only的泛化性能更好
- decoder-only支持一直复用KV-Cache,对多轮对话更友好
- 预训练任务难度问题,纯粹的decoder-only架构+next token predicition预训练,每个位置所能接触的信息比其他架构少,要预测下一个token难度更高,当模型足够大,数据足够多的时候,decoder-only模型学习通用表征的上限更高
- 双向attention的注意力矩阵容易退化为低秩状态,而causal attention的注意力矩阵是下三角矩阵,必然是满秩的,建模能力更强;
- causal attention具有隐式的位置编码功能,打破了transformer的位置不变性,而带有双向attention的模型,如果不带位置编码,双向attention的部分token可以对换也不改变表示,对语序的区分能力天生较弱。
- Encoder-Decoder架构之所以能够在某些场景下表现更好,大概只是因为它多了一倍参数。
3) Layer Normalization
在统计学中,协变量偏移指的是源域(S)和目标域(T)边缘分布的不一致,但是它们的条件分布却是相同的。对于深度学习来说,指的是训练集和测试集的输入特征分布不同(例如训练数据和测试数据拍摄条件不同),但在给定特征时,输出的分布是一致的。
- 用训练集得到的模型在测试集上做性能评估,得到的不是模型的真实水平
- 训练集和测试集分布差异过大,我们训练得到的不是真实模型
因此在机器学习中,我们期望数据是独立同分布的,这要求训练集和测试集的样本都是从同一个分布独立采样而来。 但是在深层神经网络的训练中,当中间神经层的前一层参数发生改变时,该层的输入分布也会发生改变,也就是存在内部协变量偏移ICS问题。中间的神经层需要不断适应这种变化,这会降低整个网络的收敛速度。
可以通过固定每一层网络的输入值分布来减缓ICS问题:
- 白化:通过调整输入数据的分布来减少协变量偏移的影响,比如PCA。即标准正态分布+去除相关性(协方差矩阵奇异值分解)。 1)白化过程计算成本太高,如PCA中需要计算协方差矩阵,并在每一轮训练的每一层都执行该运算; 2)白化过程改变了网络中每一层的分布,因此改变了网络层中本身数据的表达能力,底层网络学习到的参数信息会被白化操作丢失。
- 归一化:使得样本处于同一分布,且可以解决每批训练数据分布不同的问题,而且Normalization包含线性变换操作,可以让数据尽可能恢复本身的表达能力。即转换为均值为0,方差为1的标准正态分布。主要操作有:去均值、除以标准差、线性变换(缩放和平移)。
常用的Normalization方法有Batch Normalization、Layer Normalization、Instance Normalization、Group Normalization等。主要区别在于操作的特征维度不同。基本计算公式如下:
\[ y = \gamma \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]
其中,\(\gamma\)和\(\beta\)是可学习的参数,\(\mu\)和\(\sigma\)是均值和方差,\(\epsilon\)是一个很小的数,防止分母为0。
假定输入数据的维度是\((N, C, H, W)\),其中\(N\)是batch size,\(C\)是通道数,\(H\)和\(W\)是高和宽。 Batch Normalization:对整个批次的数据进行归一化。 在\(N H W\)维度上求均值和方差,保留\(C\)维度,对每个通道进行归一化。
\[ \mu_c = \frac{1}{NHW} \sum_{n=1}^{N} \sum_{h=1}^{H} \sum_{w=1}^{W} x_{nchw}\\ \sigma_c^2 = \frac{1}{NHW} \sum_{n=1}^{N} \sum_{h=1}^{H} \sum_{w=1}^{W} (x_{nchw} - \mu_c)^2 + \epsilon\\ \]
Layer Normalization:对单个样本的所有特征进行归一化。 在\(C H W\)维度上求均值和方差,保留\(N\)维度。
\[ \mu_{n} = \frac{1}{CWH} \sum_{c=1}^{C} \sum_{h=1}^{H} \sum_{w=1}^{W} x_{nchw}\\ \sigma_{n}^2 = \frac{1}{CWH} \sum_{c=1}^{C} \sum_{h=1}^{H} \sum_{w=1}^{W} (x_{nchw} - \mu_n)^2 + \epsilon\\ \]
Instance Normalization:对每个样本的每个通道独立进行归一化。 在\(H W\)维度上求均值和方差,保留\(N C\)维度。在channel内部求均值和标准差
\[ \mu_{nc} = \frac{1}{HW} \sum_{h=1}^{H} \sum_{w=1}^{W} x_{nchw}\\ \sigma_{nc}^2 = \frac{1}{HW} \sum_{h=1}^{H} \sum_{w=1}^{W} (x_{nchw} - \mu_{nc})^2 + \epsilon\\ \]
Group Normalization:对每个样本的特定组别的特征进行归一化。 将通道分为若干组,每组进行归一化。各组channel用其对应的归一化参数独立地归一化。
\[ \mu_{ng} = \frac{1}{(C/G)HW} \sum_{c=1}^{C/G} \sum_{h=1}^{H} \sum_{w=1}^{W} x_{nchw}\\ \sigma_{ng}^2 = \frac{1}{(C/G)HW} \sum_{c=1}^{C/G} \sum_{h=1}^{H} \sum_{w=1}^{W} (x_{nchw} - \mu_{ng})^2 + \epsilon\\ \]
总结:
- Batch Normalization:适用于CV领域
- 处理序列数据时表现差,因为序列数据的维度是变化的,而BN是在固定维度上进行归一化的,因此不适用于RNN、LSTM等序列模型。
- 小batch size时效果差,因为每个batch的统计特性会有较大波动
- Layer Normalization:适用于RNN、LSTM等序列模型
- 适用于序列数据,因为LN是在每个样本上进行归一化的,不受batch size影响
- 适用于序列数据,因为LN是在序列长度上对每个样本进行归一化
- 对单个样本的所有特征进行统一归一化处理,如果这些特征来自不同的类别或具有不同的性质,可能会降低模型的表达能力。
- 不适应输入变化很大的数据
- Instance Normalization:图像的风格迁移
- 对每个样本的每个特征进行归一化,因此它可以捕捉到更多的细节信息
- 可能会过度强调细节信息,忽视了更宏观的信息
- 计算成本较高,计算成本比BN和LN要高
- 不适应通道之间的相关性较强数据
- Group Normalization:适用于占用显存比较大的任务,如图像分割
- 既可以捕获到Batch的统计特性,又可以捕获到样本的细节信息,是BN和IN的折中方案
- 对Batch size不敏感
- 性能取决于组的大小,计算成本比BN和LN要高
LLM采用Layer Normalization,而又可以细分为以下几种:
按方法分类:
- LayerNorm:对单个样本的所有特征进行归一化(标准正态分布+线性变换) \[ y = \gamma \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]
- RMSNorm:通过实验证明re-center操作并不重要,移除了LayerNorm中的均值项,可以看作LayerNorm在均值为0时的一个特例。(无需计算均值,提高了训练速度) \[ y = \gamma \frac{x}{RMS(x)}\\ RMS(x) = \sqrt{\frac{1}{d} \sum_{i=1}^{d} x_i^2} \]
- DeepNorm: 不仅用作标准化,还作为残差连接的一部分,将前一层的输出与标准化后的输出相加。可以缓解爆炸式模型更新的问题。 \[ x_{l+1}=LN(\alpha x_l + G_l(x_l, \theta_l)) \]
按位置分类:
图2:Layer Normalization Position
- Post Layer Norm:在每个子层的输出后应用LayerNorm。 \(x_{l+1} = LN(x_l +
SubLayer(x_l))\)
- Post Norm结构的最终效果是要好于Pre Norm的,只不过Post Norm要达到自己的最优效果,需要加Warmup等训练技巧,如果和Pre Norm用一样的训练配置则效果不如Pre Norm。
- 上限高于Pre Norm,但是需要更多的训练技巧。
- 在深层的梯度范式逐渐增大,导致使用Post Norm的深层Transformer容易出现训练不稳定的问题
- Pre Layer Norm:在每个子层的输入前应用LayerNorm。 \(x_{l+1} = SubLayer(LN(x_l)) +
x_l\)
- 同一设置下,Pre Norm结构往往更容易训练,但是最终效果通常不如Post Norm
- 无形地增加了模型的宽度而降低了模型的深度,而在深度学习中深度通常比宽度更重要。也就是说Pre Norm的深度有“水分”,一个L层的Pre Norm模型其实际等效层数不如L层的Post Norm模型,层数少就会导致效果变差。
- 相比于Post Norm,Pre Norm在深层的梯度范式近似相等,所以使用Pre Norm的深层Transformer训练更加稳定
- Sandwich Norm:结合了Pre Norm和Post
Norm的优点,同时在输入前和输出后应用LayerNorm。 \(x_{l+1} = x_l +
LN(SubLayer(LN(x_l)))\)
- 有效控制每一层的激活值,避免它们过大,模型能够更好地学习数据特征
- 训练不稳定,可能会导致训练崩溃
4) 激活函数
FFN通常先将向量从维度d升维到中间维度4d,再从4d降维到d。计算公式如下:
\[ FFN(x) = f(xW_1 + b_1)W_2 + b_2 \]
其中,f(x)是非线性激活函数。广泛使用的激活函数有ReLU、GELU、Swish等。
\[ ReLU(x) = max(0, x)\\ GELU(x) = x \cdot \phi(x)\\ Swish(x) = x \cdot \sigma(\beta x)\\ \]
其中,\(\phi(x)\)是标准正态分布的累积分布函数,\(\sigma(x)\)是sigmoid函数。
\[ \phi(x) = \frac{1}{2} \left(1 + erf\left(\frac{x}{\sqrt{2}}\right)\right)\\ \sigma(x) = \frac{1}{1 + e^{-x}} \]
通过引入线性门控单元(GLU)来增强模型的非线性能力,FFN额外增加了一个权重矩阵,即下式中的V,共有三个权重矩阵,获得了更好的模型性能。
\[ \begin{matrix} FFN(x) = (f(xW_1) \otimes xV)W_2\\ ReGLU(x) = ReLU(xW) \otimes xV\\ GeGLU(x) = GELU(xW) \otimes xV\\ SwiGLU(x) = Swish_{\beta}(xW) \otimes xV\\ \end{matrix} \]
2 高效参数微调方法
大语言模型的参数量进行全量微调成本很高。高效参数微调(PEFT)在微调大模型时只训练一小部分参数。高效参数微调方法有以下几方面优点:
- 显存占用少,对硬件资源要求低
- 训练速度快,耗时更短
- 更低的存储成本,不同的任务可以共享大部分的权重参数
- 可能会有更好的模型性能,减轻了过拟合问题
2.1 提示微调(Prompt Tuning)
prompt tuning原本的含义指的是通过修改输入prompt来获得更好的模型效果。这里的提示是“硬提示(hard prompt)”。我们直接修改输入prompt,输入prompt是不可导的。例如,我们可以将输入prompt从“What is the capital of France?”修改为“Question: What is the capital of France? Answer:”,引导模型生成想要的输出。
与“硬提示”相对应,“软提示微调(soft prompt tuning)”将一个可训练张量与输入文本的embeddings拼接起来,这个可训练张量可以通过反向传播来优化,进而提升目标任务的模型效果。这里的可训练张量可以理解为prompt文本对应的embedding,是一个soft prompt。冻结大模型原始的参数,只训练这个新增加的prompt张量。prompt tuning随着基座模型参数量的增大效果会变好。 尺寸:\([virtual\_token\_num, embd\_dim]\)
实际上都只是改变的输入文本,不改变LLM Model参数,但是硬提示是通过手动设计的自然语言提示来引导模型,通过改变输入文本来间接影响嵌入表示,而软提示通过可训练的提示嵌入直接影响输入表示,能够进行优化和微调,适用于更复杂和多样化的任务。
2.2 前缀微调(Pre-fix Tuning)
prefix tuning与prompt tuning相似,将一个特定任务的张量添加到输入,这个张量是可训练的,保持预训练模型的参数不变。主要区别如下:
- prefix tuning将prefix参数(可训练张量)添加到所有的transformer层的输入(也许就是传播后的embedding?),而prompt tuning只将可训练矩阵添加到输入embedding。prefix tuning会将prefix张量作为past_key_value添加到所有的transformer层。每个transformer层都有各自不同的可学习prefix,允许不同模型层进行更量身定制的适应。
- 用一个独立的FFN来编码和优化prefix参数,而不是直接优化soft prompt,因为它可能造成不稳定并损害性能。在更新完soft prompt后,就不再使用FFN了。
前者是直接作用在输入embedding上,后者是作用在所有transformer层的self-attention块,在计算得到K和V后,与可训练的prefix张量拼接起来。
尺寸:\([virtual\_token\_num, 2 \times layer\_num \times hidden\_size]\) (2是因为K和V)
2.3 适配器微调(Adapter Tuning)
图3:Adapter Tuning
首先是一个 down-project 层将高维度特征映射到低维特征,然后过一个非线形层之后,再用一个 up-project 结构将低维特征映射回原来的高维特征; 同时也设计了 skip-connection 结构,确保了在最差的情况下能够退化为 identity 在训练时,固定住原来预训练模型的参数不变,只对新增的 Adapter 结构进行微调
2.3.1 LLaMA-adapter
LLaMA-adapter结合了prefix tuning和adapter。与prefix tuning类似,LLaMA-adapter在输入embed上添加了可训练的prompt张量。
- LLaMA-adapter引进了零初始化的注意力机制和门控机制。动机是adapter和prefix tuning结合了随机初始化的张量(prefix prompts和adapter layers)很大可能会损害预训练语言模型的语义学知识,导致在训练初始阶段的微调不稳定和很高的性能损失。
- LLaMA-adapter只给L个深层transformer层添加了可学习的adaption prompts,而不是给所有的transformer层都添加。作者认为,这种方法可以更有效的微调专注于高级语义信息的语言表示。
2.4 低秩适配微调(Low-Rank Adaptation, LoRA)
图4:LoRA
冻结了预训练的模型权重,并将可训练的秩分解矩阵(LoRA参数)注入到 Transformer 架构的每一层中。
假设模型原有参数为\(W_0 \in \mathbb{R}^{d \times k}\),微调后的参数为\(W \in \mathbb{R}^{d \times k}\),即
\[ W = W_0 + \Delta W \]
LoRA将\(\Delta W\)分解为两个低秩矩阵的乘积,即:
\[ \Delta W = B \cdot A \]
其中,\(B \in \mathbb{R}^{d \times r}\),\(A \in \mathbb{R}^{r \times k}\),\(r\)是低秩矩阵的秩,且\(r \ll min(d,k)\)。
F.linear(input, self.weight, self.bias) + (self.lora_dropout(input) @ self.lora_right_weight @ self.lora_left_weight) * self.lora_scaling
- \(B\)是升维矩阵,将低维的输入向量升维到高维空间,\(A\)是降维矩阵,将高维的输出向量降维到低维空间。 矩阵的”升维”或”降维”作用取决于它在整个运算中的位置和作用,而不仅仅是它自身的维度。即\(B\)的维度是\(d \times r\),这意味着它将\(r\)维的输入向量变换到\(d\)维的输出向量。而\(A\)的维度是\(r \times k\),这意味着它将\(d\)维的输入向量变换到\(r\)维的输出向量。
- 为什么矩阵B被初始化为0,而矩阵A正常高斯初始化 全0容易梯度消失(因为对称性),全高斯初始化会引入过大噪声,所以B初始化为0,A初始化为高斯,是为了在训练开始时维持网络的原有输出(初始偏移为0),但同时也保证在真正开始学习后能够更好的收敛(打破对称性)。
- \(\Delta W\) 会通过\(\frac{\alpha}{r}\)的方式进行缩放,其中\(\alpha\)类似调整学习率,所以不如固定为1,只调节\(r\)。
- 与adapter tuning相比,LoRA的区别在于:
- 插入位置。LoRA是以残差连接的形式”并联”在Transformer的Q,K,V,O矩阵上(有研究表明只用于QV表现也不错);而Adapter是插入在Feed-forward Layer后面。
- 推理延迟。LoRA在训练完后其参数可以与原有预训练模型直接合并,变回单分支结构,不会引入额外的延迟;而Adapter由于引入了额外的串联网络层,因此会带来额外的延迟。
- 参数存储。使用LoRA进行微调,在训练完毕后只需要保存LoRA本身的参数;而使用Adapter则要保存整个原有模型的参数。
2.4.1 模型量化(Quantization)
模型量化是一种压缩网络参数的方式,它将神经网络的参数(weight)、特征图(activation)等原本用浮点表示的量值换用定点(整型)表示,在计算过程中,再将定点数据反量化回浮点数据,得到结果。这样可以减少模型的存储空间和计算量,提高模型的运行速度。
- 可以减少内存和显存占用,给模型瘦身
- 能够提高运行速度,这可以从两方面理解
- 在适配低精度的硬件下,量化模型的运算能直接用 int8 GEMM kernel 计算
- 量化减少了单位数据的 bit 数,因而可以减少计算过程中的 IO 通信量
- 可以增大 batch size
量化方法
- QAT(Quant-Aware Training)也可以称为在线量化(On Quantization)。它需要利用额外的训练数据,在量化的同时结合反向传播对模型权重进行调整,意在确保量化模型的精度不掉点
- PTQ (Post Training Quantization)也可以称为离线量化(Off
Quantization)。它是在已训练的模型上,使用少量或不使用额外数据,对模型量化过程进行校准,可能伴有模型权重的缩放。
- 训练后动态量化(PostDynamic Quantization),不使用校准数据集,直接对每一层 layer 通过量化公式进行转换,QLoRA 就是采用这种方法
- 训练后校正量化(Post Calibration Quantization),需要输入有代表性的数据集,根据模型每一层 layer 的输入输出调整量化权重,GPTQ 就是采用这种方法。
计算公式如下:
\[ \begin{align} {quantized\_value} = {round}(\frac) + {zero\_point}\\ {float\_value} = ({quantized\_value} - {zero\_point}) \times {scale} \end{align} \]
通常 scale 为 \(\frac{max(abs(float\_value))}{2^{bit\_width-1}-1}\)
图5:Quantization
对称量化 量化前后的 0 点是对齐的,因此不需要记录零点。它适合对分布良好且均值为 0 的参数进行量化(对于正负数不均匀分布的情况不够友好),因此对称量化常用于对 weight 量化 非对称量化 量化前后 0 点不对齐,需要额外记录一个 offset,也就是零点。非对称量化常用于对 activation 做量化
为考虑其他更多情况,需要引入一种新的量化策略:Block-wise quantization(块级量化),块级量化最终能使量化的精度损失减少。为了避免异常值(outlier)的影响,我们会将输入tensor(通常是神经网络的权重或激活值)分割成多个块(block),然后每个块(block)单独做量化,有单独的缩放因子scale和零点zero
- 可以为不同的数据块选择不同的量化策略。例如,某些块可以用更高的位宽量化,而其他块则可以用更低的位宽
- 尽管块级量化可以提供更好的精度,但由于每个块都有自己的量化参数,这可能会增加计算和存储的开销
2.4.2 量化低秩适配(QLoRA)
QLoRA 针对模型权重(weight)做量化,采用的是对称量化算法,量化过程基本与LoRA相同:
- 4位NormalFloat量化:确保每个量化仓中有相同数量的值 即采用新的 NF (NormalFloat)数据类型,它是对于正态分布权重而言信息理论上最优的数据类型,同时,NF 类型有助于缓解异常值的影响
- 双量化:对量化常量再次量化以节省额外内存的过程 即Double Quant,对于量化后的 scale 数据做进一步的量化
- QLoRa还有统一内存分页:它依赖于NVIDIA统一内存管理,自动处理CPU和GPU之间的页到页传输,它可以保证GPU处理无错,特别是在GPU可能耗尽内存的情况下
NF数据类型
int4 的格点分布是均匀的,然而模型的权重通常服从均值为 0 的正态分布,因此格点的分布和数据的分布不一致。 NF4 的格点按照正态分布的分位数截取,格点分布两端稀疏,中间密集,格点分布与数据分布一致。这样格点分配的效率就大大增加了,同时精度受损也不会太大。
双量化(Double Quant)
QLoRA 将每 64 个参数为做一个 block,即 block_size = 64,每个 block 计算一个 Scale。 由于量化后的 Scale 通常以 FP32 存储,在 block 数众多的情况下,Scale 占用的显存也不可忽视。 因此,QLoRA 对 Scale 进一步量化成 FP8,取 Double Quant 的 block size = 256,因而进一步降低了显存消耗。






