LLM-3 微调

指令微调(核心思想是执行特定任务时,只有一小部分参数起作用。)

主成分分析和压缩感知是什么?
(1)明确任务的指令或提示。
(2)整理数据为指令和响应。
(3)对预训练模型进行微调。

构建微调数据集的四种方法

(1)手动:

  • 网页收集:InstructionWild v2,LCCC
  • 真人专门构建:Databricksdolly-15K,OASST1,OL-CC,Aya Dataset

(2)现有数据集转换:合并多个数据集,产生了的定义歧义以及数据冗余的问题。B2NERD的解决方法:1,找到冲突的实体,然后重新打标.2,选择均匀的样本。
image

(3)自动构建:代表作Self-Instruct
生成任务中:种子指令集中有答案吗?提示词是设定环境,场景。输入是问题
指令与输入不是一个东西?

  • 手动构建小型指令数据集,作为种子指令集。利用模型自举方式来生成更多的新任务的指令:每次从指令池中采样 8条任务指令(其中 6 条来自人工编写的种子指令,2 条是模型迭代生成的),将其拼接为上下文示例,引导预训练语言模型 GPT-3 生成更多的新任务的指令,直到模型自己停止生成,或达到模型长度限制,或是在单步中生成了过多示例(例如当出现了“Task 16”时)。
  • 利用模型本身,将任务进行分类:分为分类任务和非分类任务。
  • 指令以及构建完成,接下来要生成相关任务的输入和输出,就是具体的问题,可以看成cpp中的抽象和实体?具体来说是使用来自其他任务的“指令”“输入”“输出”上下文示例做提示,预训练模型就可以为新任务生成输入–输出对。
  • 过滤低质量数据:指令相似度计算与过滤某些关键词。

指令微调数据评估

一般1000条高质量数据集是足够的。

指令微调策略

采用四种指令微调策略:多任务,多阶段,混合训练,双阶段混合
image
其中,多任务训练保持了特定任务的能力,但是损失了通用能力。顺序训练和混合顺序训练保持了通用能力,但是损失了特定任务的能力。以上三种都或多或少出现灾难性遗忘的问题。综合来说双阶段混合训练是最合适的。一般最后阶段选取大约\(\frac{1}{256}\)领域数据和所有的通用数据,能有效缓解,微调后出现灾难性遗忘的问题。

LoRA

公式

\(h=W_0x+\theta Wx = W_0x+BAx\)
\(A\)通过高斯初始化,\(B\)矩阵零初始化,使得开始时旁路对原模型不造成影响。
image

LoRA相较于其他的微调方法对比

微调适配器:在Transformer层中的自注意力模块与多层感知模型以及多层感知模块与残差连接之间增加适配器。缺点:增加了网络深度,推理带来额外开销。
前缀微调:在输入问题前缀添加连续可微的软提示作为可训练参数。因为最大输入长度优先,影响性能。
LoRA均优于上述两种。

1.2 过参数化
现在深度学习的参数动不动就有几百万,LLM的参数更是数十亿起步。许多工作[2]已经表明,深度学习的矩阵往往是过参数化的(over-parametrized)。特征的内在维度(intrinsic dimension)指的是在深度学习中的真实或潜在的低维结构或信息的维度。它表示特征中存在的有效信息的维度,与特征的实际维度可能不同。事实上许多问题的内在维度比人们认为的要小的多,而对于某个数据集,内在维度在不同参数量级的模型上差距并不大。这个内在维度指的是我们解决这个问题实际上需要的参数空间的维度,我们对模型的微调通常调整的也是这些低秩的内在维度。这个结论说明了两个现象:

一旦我们找到了足够解决问题的参数空间,再增加这个参数空间的大小并不会显著提升模型的性能。
一个过参数的模型的参数空间是有压缩的空间的,这也就是LoRA的提出动机。

为解决微调大规模语言模型到不同领域和任务的挑战,已有多种方案,比如部分微调、使用adapters和prompting。但这些方法存在如下问题:

Adapters引入额外的推理延迟 (由于增加了模型层数)
Prefix-Tuning难于训练,且预留给prompt的序列挤占了下游任务的输入序列空间,影响模型性能
https://www.zhihu.com/tardis/zm/art/623543497?source_id=1003
在机器学习中,解耦可以指将模型的架构与具体的训练目标分开,如使用LoRA等技术进行参数调整,使得模型可以应用于多种任务,而不需要重新训练整个模型。

实战训练

从lora应用的代码可以看出,是将原模型进行魔改,然后训练。
image
CAUSAL_LM表示因果语言模型:因果语言模型的目标是预测序列中下一个单词的概率,基于该序列之前的所有单词。
源代码难以理解原因是处于代码规范的关系。
peft中对lora代码的实现(看不懂,笑)
这来回的矩阵转置人看晕了

点击查看代码
class LoRALayer():
    def __init__(
        self, 
        r: int, 
        lora_alpha: int, 
        lora_dropout: float,
        merge_weights: bool,
    ):
        self.r = r
        self.lora_alpha = lora_alpha
        # Optional dropout
        if lora_dropout > 0.:
            self.lora_dropout = nn.Dropout(p=lora_dropout)
        else:
            self.lora_dropout = lambda x: x
        # Mark the weight as unmerged
        self.merged = False
        self.merge_weights = merge_weights


class Linear(nn.Linear, LoRALayer):
    # LoRA implemented in a dense layer
    def __init__(
        self, 
        in_features: int, 
        out_features: int, 
        r: int = 0, 
        lora_alpha: int = 1, 
        lora_dropout: float = 0.,
		#矩阵转置行为是啥意思?
        fan_in_fan_out: bool = False, # Set this to True if the layer to replace stores weight like (fan_in, fan_out)?啥意思
        merge_weights: bool = True,
        **kwargs
    ):
        nn.Linear.__init__(self, in_features, out_features, **kwargs)
        LoRALayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout,
                           merge_weights=merge_weights)

        self.fan_in_fan_out = fan_in_fan_out
        # Actual trainable parameters
        if r > 0:
            self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features)))
            self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r)))
            self.scaling = self.lora_alpha / self.r
            # Freezing the pre-trained weight matrix
            self.weight.requires_grad = False
        self.reset_parameters()
        if fan_in_fan_out:
            self.weight.data = self.weight.data.transpose(0, 1)

    def reset_parameters(self):
        nn.Linear.reset_parameters(self)
        if hasattr(self, 'lora_A'):
            # initialize A the same way as the default for nn.Linear and B to zero
            nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
            nn.init.zeros_(self.lora_B)

    def train(self, mode: bool = True):
        def T(w):
            return w.transpose(0, 1) if self.fan_in_fan_out else w
        nn.Linear.train(self, mode)
        if mode:
            if self.merge_weights and self.merged:
                # Make sure that the weights are not merged
                if self.r > 0:
                    self.weight.data -= T(self.lora_B @ self.lora_A) * self.scaling
                self.merged = False
        else:
            if self.merge_weights and not self.merged:
                # Merge the weights and mark it
                if self.r > 0:
                    self.weight.data += T(self.lora_B @ self.lora_A) * self.scaling
                self.merged = True       

    def forward(self, x: torch.Tensor):
        def T(w):
            return w.transpose(0, 1) if self.fan_in_fan_out else w
        if self.r > 0 and not self.merged:
            result = F.linear(x, T(self.weight), bias=self.bias)            
            result += (self.lora_dropout(x) @ self.lora_A.transpose(0, 1) @ self.lora_B.transpose(0, 1)) * self.scaling
            return result
        else:
            return F.linear(x, T(self.weight), bias=self.bias)

 

参考网站
实战网站
https://github.com/huggingface/blog/blob/main/zh/Lora-for-sequence-classification-with-Roberta-Llama-Mistral.md
https://llamafactory.readthedocs.io/zh-cn/latest/getting_started/data_preparation.html
https://www.aqwu.net/wp/?p=2567

LoRA的变种

AdaLoRA

需要理解奇异值分解
LoRA对模型中所有要更新的参数都固定r值,忽略了不同模块、不同参数对于微调特定任务的差异性。所以需要设计一种在微调过程中根据各权重矩阵对下游任务的重要性动态调整秩的大小,用以进一步减少可训练参数量,同时保持或提高性能。

实现方法:对变更的参数矩阵做奇异值分解,通过估计每轮的奇异值的重要性来不断变更矩阵的秩的大小。

具体公式:没搞明白,暂时不写。

QLoRA

理解分位数量化是什么?什么是第一分位数?
4比特量化是将目前的取值区间投影成16个数值,这16个区间的投影的概率相同
image
分块量化:对特征张量,进行分组,按组内进行量化,并且保留相关信息
image
QLoRA的核心就是:将模型的参数(全部的参数?还是要变化的参数?)进行分块分数位量化,再将量化常数再次量化,可以极大的压缩模型的参数。显存占用过高时,占用内存,一共三个技术。
(1)新的数据类型 4-bit NormalFloat(NF4)。
(2)双重量化(Double Quantization)。
(3)分页优化器(Paged Optimizer)。分
小问题:分位数的区间的0的问题,偏移问题?怎么计算优化之后的显存占用的多少,数值计算?
https://zhuanlan.zhihu.com/p/666234324

模型上下文窗口拓展

三种方法:

增加上下文窗口的微调

采用更长的上下文来微调现有的Transformer:缺点:模型上下文窗口只有小幅度的增长,从 2048 增加到 2560。实验结果显示,这种朴素的方法在扩展到更大的上下文窗口时效率较低。评价:没啥用

位置编码

ALiBi

啥啥啥?

插值法

看完RoPE再来吧。看不懂你。

posted @ 2025-04-21 14:10  keepsoft123  阅读(53)  评论(0)    收藏  举报