训练与微调技术

1.基本概念

1.什么是微调?

微调是一种迁移学习的方法。基于预训练模型,在特定任务的数据上进行有监督的训练(SFT),以适应任务需求。微调可以将预训练模型在大规模通用数据上学习到的知识迁移到特定任务重。

微调的一般步骤包括:(1)选择预训练模型;(2)准备微调数据;(3)构建任务特定的模型头(task-specific head);(4)加载预训练模型的参数;(5)设置超参数并执行微调;(6)评估,验证和二次微调。

  • Task-specific head:添加到预训练模型之上的额外结构,用于根据任务需求进行输出预测或分类。

2.全量参数微调与参数高效微调有什么区别?

全量微调:随着预训练大模型的参数规模越来越大,这使得在消费级硬件上进行全量微调变得不可行。比如:3B参量级的大模型全量微调大约需要48GB的GPU资源,而占用的CPU内存大约在3GB左右。除此之外,模型的全量微调还会导致多样性的损失,甚至发生灾难性遗忘等问题。

参数高效微调(PEFT):参数高效微调是指微调少量或额外的模型参数。该方法固定大部分预训练模型(LLM)参数,大大降低了计算和存储成本的同时,实现与全量参数微调相当的性能。高效微调技术可以粗略分为以下三大类:增加额外参数(A)选取一部分参数更新(S)引入重参数化(R)

3.参数高效微调(PEFT)有什么优点?

相较于传统的全量微调,PEFT的好处有以下几点:

  • 减少计算和存储成本:PEFT只涉及微调少量的模型参数,显著降低了计算和存储成本。

  • 克服灾难性遗忘:全量微调可能导致模型忘记预训练期间学到的知识,而PEFT只更新少量参数,而不是对整个模型进行微调,能够有效减少对预训练知识的遗忘。

  • 数据稀缺场景的泛化性:在数据稀缺的情况下,PEFT方法能够让模型保持更好的性能。

  • 可移植和稳定的性能:PEFT方法在性能上与全量微调相当,但仅产生MB级的小检查点文件。这使得训练好的权重更容易在不同设备上进行部署。

4.现有的参数高效微调(PEFT)如 LoRA、Adapter Tuning、Prompt Tuning 等有什么问题?

  1. 潜在的性能上限低于全量微调 (Potential Performance Ceiling Below Full Fine-tuning):
    • 问题: PEFT 方法通过只更新模型参数的一个小子集来实现效率。虽然在许多任务上 PEFT 可以达到与全量微调(更新所有参数)相当甚至更好的性能,但在某些复杂或与预训练数据分布差异较大的任务上,限制可训练参数的数量可能会导致模型无法充分适应新任务,从而性能略低于精心调优的全量微调。
    • 原因: 全量微调拥有更大的参数空间来学习任务特定的特征和模式。PEFT 只能在有限的低维空间或通过修改输入/中间层来影响模型行为,这可能不足以捕捉所有必要的适应性。
  2. 超参数调优的复杂性 (Hyperparameter Tuning Complexity):
    • 问题: 虽然 PEFT 减少了需要更新的参数数量,但它引入了新的超参数,例如 LoRA 的秩 (rank) 和缩放因子 (alpha),Adapter 的瓶颈大小,Prompt Tuning 的 Prompt 长度和初始化方式等。这些超参数对最终性能影响很大,且其最优值通常依赖于具体的模型、任务和数据集。
    • 原因: 找到这些新超参数的最佳组合可能需要大量的实验和计算资源,这在一定程度上抵消了 PEFT 在训练本身带来的效率优势。有时,调优 PEFT 的超参数可能比调优全量微调的少数几个关键超参数(如学习率)更具挑战性。
  3. 对任务和领域的依赖性 (Task and Domain Dependence):
    • 问题: 不同的 PEFT 方法可能更适合不同类型的任务或领域。例如,Prompt Tuning 在某些分类和生成任务上表现良好,但可能不适合需要深入修改模型内部表示的任务。Adapter Tuning 可能在跨领域适应方面有优势。LoRA 在多种任务上表现通用,但其最优配置仍需针对具体任务调整。
    • 原因: 不同的 PEFT 方法修改模型的方式不同(修改输入、插入层、修改权重矩阵等),这决定了它们更擅长影响模型的哪些方面。没有一种 PEFT 方法是“万能”的,需要根据具体应用场景选择和调优。
  4. 与预训练模型架构的耦合 (Coupling with Pre-trained Model Architecture):
    • 问题: 大多数 PEFT 方法是为特定的模型架构(主要是 Transformer)设计的,并且通常需要访问模型的内部结构(例如,Transformer 块中的特定矩阵)。这使得将 PEFT 方法应用于新型或非标准架构的模型可能比较困难,或者需要进行大量的修改。
    • 原因: PEFT 方法通常直接作用于预训练模型的权重或层,其设计紧密依赖于这些层的类型和连接方式。

2.指令微调

1.指令微调数据制作发展路线

  1. Scaling law:在指令微调数据较为匮乏的时期,收集更多的数据是提升性能的大力出奇迹办法。

  2. 人工和启发式的数据多样性:在数据量积累到一定规模后,数据混合配比成为新的研究话题。一些研究成果验证了合适的数据配比可以提升性能,但数据配比没有通用的万能钥匙。

  3. 基于模型的多样性:随着LLMs/MLLMs,可以让它们参与到数据生产和筛选流程中,例如用GPT3.5/4/4V生产数据,用其它LLMs作为数据质量筛选器。(GPT4/GPT4V为指令微调领域贡献了太多数据,这可能也是一种OpenAI吧)

  4. 数据效率:有了LLMs/MLLMs的加持,数据量似乎已经不成大问题。因此高质量数据的多样性、难度和复杂程度成为了关注焦点。满足上述要求的数据意味着用高质量的响应近似真实用户提示,LIMA论证了只要数据质量足够高,数据量会是次要因素。因此,需要自动化或半自动方案对数据进行过滤:

    1. 基于自然语言规则过滤;

    2. 用InsTag对指令微调数据打语义或意图的标签,从而做聚类分析;

    3. 利用GPT4等语言模型过滤噪声数据;

    4. 利用模型的loss等反馈数据对模型的影响,例如评估模型对指令的不确定性(Active Instruction Tuning);

  5. 数据治理、责任和其他问题:开始关注数据商业条款、许可,版权等问题。

2.指令微调数据构造有哪些指导原则?

  1. 多样性:覆盖尽可能多的数据/能力/响应类型;

  2. 高质量:Less is More,最好由算法工程师人工检查每一条指令微调数据,保证每条数据的高质量,三个臭皮匠抵不过一个诸葛亮;

  3. 复杂性:提高每条数据的信息量;

  4. 每种能力的激活不需要太多数据

  5. 更自由的强指令跟随能力需要较多数据

  6. 精调各项能力配比,避免遗忘;

3.经过监督微调SFT后LLM表现下降的原因是什么?

SFT(Supervised Fine-Tuning)是一种常见的微调技术,它通过在特定任务的标注数据上进行训练来改进模型的性能。在经过监督微调(SFT)后,语言模型(LLM)性能下降可能是由以下几个原因导致的:

过拟合(Overfitting)
- 原理:在监督微调过程中,如果训练数据量相对模型的容量过小,或者训练轮数过多,模型可能会过度学习训练数据中的细节和噪声,而无法很好地泛化到新的数据。例如,模型可能记住了训练数据中的一些特定的回答模式或文本片段,而不是学习到通用的语言规则和语义理解。
- 举例:假设微调一个用于情感分类的LLM,训练数据集中有很多包含特定电影名称的影评,模型可能会过度关注这些电影名称与情感标签之间的关联。当遇到不包含这些电影名称的新影评时,模型的性能就会下降。
数据质量问题
- 标注错误(Labeling Errors):监督微调依赖于标注数据来学习任务特定的知识。如果标注数据中存在错误,例如将正面情感的文本标注为负面,那么模型在学习这些错误标注后,性能会受到影响。例如,在一个文本翻译任务中,错误的翻译标注会引导模型学习到错误的翻译规则。
- 数据分布偏差(Data Distribution Bias):如果微调数据的分布与预训练数据或者实际应用场景中的数据分布差异过大,模型可能会在新的数据分布上表现不佳。比如,预训练数据涵盖了各种主题和风格的文本,但微调数据主要集中在某一特定领域,那么模型在其他领域的文本处理上性能可能会下降。
模型结构和超参数问题
- 微调策略不当(Improper Fine - tuning Strategy):选择的微调方法可能不适合当前的任务或模型。例如,微调时学习率设置过高,可能会导致模型在优化过程中跳过最优解,甚至使模型参数变得混乱,无法有效学习到有用的信息。
- 模型架构冲突(Model Architecture Mismatch):某些LLM的架构可能在预训练阶段针对通用语言知识进行了优化,在监督微调时,如果任务的性质与预训练的目标差异很大,模型可能无法很好地适应。例如,一个以生成式预训练为主的模型,在微调用于判别式任务(如文本分类)时,如果没有适当的调整,可能会出现性能问题。
灾难性遗忘(Catastrophic Forgetting)
- 原理:在LLM进行监督微调时,模型可能会忘记在预训练阶段学到的通用语言知识,而过度关注微调任务相关的知识。因为模型的参数在微调过程中被更新,新的任务知识可能会覆盖或干扰之前学到的知识。
- 举例:一个已经预训练好的语言模型,在预训练阶段学会了很好的语法知识和词汇语义。在进行监督微调用于某个特定领域(如医学文献翻译)后,可能会忘记一些通用词汇在其他语境下的正确用法,从而导致在处理非医学文本时性能下降。

可以使用一些策略进行改善,如:

  • 使用更小的学习率进行微调,以减少模型对预训练知识的遗忘。
  • 使用正则化技术,如权重衰减或者早停,以防止模型过度适应微调数据。
  • 使用Elastic Weight Consolidation(EWC)等技术,这些技术试图在微调过程中保留模型在预训练阶段学到的重要知识。

4.怎么缓解模型的遗忘(表现下降)?

  • 保留部分通用数据:在进行模型微调时,保留部分通用数据用于模型微调。

  • 增量训练策略:将微调数据与通用数据逐步交替进行训练。

  • 强化学习:通过给模型设置奖励机制,鼓励模型在领域任务上表现好,同时保持一定的通用能力。

    • 以下是一些在 LLM 的 RL 训练中常用的缓解遗忘的技术:
      1. 混合训练 (Mixed Training / Combined Training):
        • 原理: 这是最常用且有效的方法之一。在进行 RL 训练时,不仅仅使用 RLHF 的偏好数据来更新模型,还会混合使用一部分来自之前训练阶段的数据,特别是指令微调阶段的数据。
        • 实现: 在每个训练批次中,一部分样本是用于计算 RL 损失(基于奖励信号),另一部分样本是来自指令微调数据集,用于计算标准的监督学习损失(例如,预测下一个词的交叉熵损失)。总的训练目标是 RL 损失和监督学习损失的加权和。
        • 作用: 通过不断地“复习”指令微调数据,模型能够保持对各种指令的理解和执行能力,防止 RL 训练过度偏离之前的知识和技能。这有助于平衡学习新的偏好行为和保留旧的能力。
      2. 正则化 (Regularization):
        • 原理: 在 RL 训练的损失函数中添加正则化项,以惩罚那些导致模型参数发生剧烈变化的更新,特别是那些对之前任务很重要的参数。
        • 实现:
          • L2 正则化 (Weight Decay): 对模型权重的大小进行惩罚,鼓励权重保持较小的值,从而限制模型复杂度和参数变化幅度。
          • 弹性权重整合 (Elastic Weight Consolidation, EWC): 这种方法会估计模型参数对之前任务的重要性(通过计算 Fisher Information Matrix),然后在训练新任务时,对重要参数的变化施加更大的惩罚。这使得模型更倾向于在对旧任务影响较小的参数空间中进行更新。
          • 学习不遗忘 (Learning without Forgetting, LwF): 使用旧模型在当前 RL 训练数据上的输出来作为“软目标”,鼓励新模型在这些数据上产生与旧模型相似的输出分布。这有助于保持模型在输入空间中的整体行为模式。
        • 作用: 通过限制参数更新的自由度,正则化有助于防止模型在学习新行为时“忘记”旧的知识。
      3. KL 散度惩罚 (KL Divergence Penalty):
        • 原理: 在 RL 训练中(特别是使用 PPO 等算法时),通常会有一个 KL 散度项来衡量当前策略(模型)与之前策略(或初始策略)之间的差异。这个项会惩罚策略的剧烈变化。
        • 实现: PPO 的目标函数通常包含一个项,如 -kl_coef * policy_kl(current_policy, old_policy),其中 policy_kl 计算两个策略输出概率分布的 KL 散度。
        • 作用: 虽然这个项的主要目的是保证 RL 训练的稳定性,防止策略更新过大导致崩溃,但它客观上也起到了一定的正则化作用,限制了模型行为的剧烈改变,从而间接有助于缓解遗忘。
      4. 参数高效微调 (Parameter-Efficient Fine-Tuning, PEFT) 在 RL 阶段的应用:
        • 原理: 在进行 RLHF 训练时,不更新模型的全部参数,而是只更新通过 PEFT 方法引入的一小部分参数(如 LoRA 的低秩矩阵、Adapter 层等)。
        • 实现: 将 PEFT 方法应用于预训练/指令微调后的模型,然后在 RL 阶段只训练 PEFT 参数和可能的头部层。
        • 作用: 由于可训练的参数数量大大减少,模型能够“遗忘”的能力也受到了限制。主要的预训练和指令微调学到的知识被保留在冻结的参数中,而 RL 训练只在有限的参数空间内调整模型的行为以适应新的奖励信号。
      5. 经验回放 (Experience Replay) / 缓冲区 (Buffer):
        • 原理: 维护一个缓冲区,存储模型在之前与环境(或人类反馈模拟器)交互时产生的“经验”(例如,Prompt、模型回复、奖励信号)。在训练时,除了使用当前的经验,还会从缓冲区中采样旧的经验进行训练。
        • 实现: 在 RLHF 中,可以存储之前迭代中模型对 Prompt 的回复和对应的奖励信号。在后续训练中,重新使用这些旧的 (Prompt, Reply, Reward) 三元组进行训练。
        • 作用: 通过重复学习旧的经验,有助于巩固模型之前学到的行为和知识,防止它们被新的经验完全覆盖。
  • 数据重采样:确保模型在微调过程中能够更多地接触到通用数据,从而缓解遗忘通用能力的问题。

5.模型微调阶段样本量规模增大导致的OOM错误原因是什么?

全参数微调的显存需求取决于多个因素,包括模型的大小(参数数量),批次大小,序列长度,以及是否使用了混合精度训练等。对于GPT-3这样的大模型,如果想要在单个GPU上进行全参数微调,可能需要数十GB甚至上百GB的显存。

当样本量规模增大时,可能会出现OOM(Out of Memory)错误,这是因为模型需要更多的内存来存储和处理数据。

一般来说导致OOM(Out of Memory)错误的原因主要有以下几点:

  • 数据存储需求增加:随着样本量的增大,需要加载到内存中的数据量也相应大幅增加。模型在训练时需要同时存储输入数据、标签数据以及可能的一些预处理后的中间结果等,这会快速耗尽内存资源。例如,对于图像数据,每个样本可能本身就具有较大的尺寸和数据量,大量样本积累起来会占用巨额内存.
  • 模型参数更新的内存开销:在微调过程中,模型需要计算每个样本的梯度来更新参数。更多的样本意味着更多的梯度计算和参数更新步骤,这会导致存储梯度信息和临时计算结果所需的内存增加。而且,一些优化算法在更新参数时还可能需要额外的内存来存储中间状态信息.
  • 批量大小的影响:通常情况下,为了加快训练速度和提高模型性能,会使用一定大小的批量数据进行训练。当样本量增大时,如果保持较大的批量大小,那么每次迭代需要处理的样本数量就更多,内存消耗也会呈倍数增长。即使批量大小保持不变,由于总样本量的增加,在整个训练过程中累计的内存占用也会逐渐超出限制.
  • 模型复杂度与参数数量:大型的预训练模型本身具有大量的参数,在微调时这些参数需要与大量的样本数据进行交互计算。当样本量进一步增大时,模型参数与样本数据之间的计算量和内存需求会急剧上升。例如,像GPT-3这样的大型语言模型,其参数量巨大,微调时即使是中等规模的样本量增加,也可能导致内存不足.
  • 硬件限制:即使数据存储和计算本身理论上所需的内存并未超出系统总内存,但计算机硬件设备(如GPU显存)存在着固定的容量限制。当样本量规模增大到一定程度,硬件设备无法提供足够的内存来满足模型训练的需求,就会出现OOM错误.
    为了解决这个问题,可以尝试以下方法:
  • 减小批量大小:这可以减少每次训练需要处理的数据量,从而减少内存使用。
  • 使用梯度累积:这种方法可以在不减小批量大小的情况下,减少内存使用。
  • 使用模型并行:这种方法可以将模型的不同部分放在不同的设备上进行训练,从而减少每个设备需要的内存。

6.微调的基座模型选择Chat还是Base模型?

如果监督任务涉及对话生成(如生成对话回复、对话情感分类等),建议选择 Chat 模型作为基座模型。如果监督任务是单轮文本生成(如新闻标题生成、文章摘要等)或非对话生成任务,建议选择 Base模型作为基座模型。

3.LoRA

1.简要介绍一下LoRA

LoRA全称Low Rank Adaptation,出自论文《LoRA: Low-Rank Adaptation of Large Language Models》。

LoRA的出发点是:预训练模型的参数量太大,而事实上对下游任务的微调所需要的本征维度(Intrinsic Dimension)并不高。

假设预训练参数 $W_0$ ,微调后的参数为 $W_1$ ,参数更新可以表示:

$$W_1 = W_0 + \Delta W$$

在“本征维度较低”假设下,可以将$\Delta W$做低秩分解:

$$W_1 = W_0 + UV$$

其中 $U \in {\mathbb R}^{m \times r}$;$V \in {\mathbb R}^{r \times n}$ 。 $r$ 可以设置得非常小,而后在微调过程中只微调 $UV$ 。
这样需要被微调的参数量就少了很多很多。

  • 原始权重 $W_0$ 的维度是 $d \times k$。
  • 引入的低秩矩阵 $U \in \mathbb{R}^{d \times r}$ 和 $V \in \mathbb{R}^{r \times k}$。
  • LoRA 的总参数量是 $r \times d + r \times k = r(d+k)$。

在实践中,要保证模型初始为预训练状态以获得一个好的微调起点,例如将$UV$之一做全0初始化,或者在 $W_0$ 中先减去 $UV$ .

2.简要介绍一下LoRA的问题以及常见的LoRA改进方案

LoRA的低秩思路显著提升了微调效率,但同时也受到低秩限制,微调性能和全参微调还是存在差距。

(1)PLoRA

PLoRA的改进思路是通过多阶段累积低秩矩阵来逼近全参微调性能。
PLoRA在每个训练阶段做一次LoRA微调并在阶段结束时将训练得到的LoRA合并到主干参数中,然后重新初始化LoRA状态。

(2)LoRA+

**LoRA+**可以为UV矩阵设置不同的学习率。

3.LoRA训练时的Rank和alpha作用是什么?

rank 是矩阵的秩,它决定了模型在微调过程中允许模型学习的新特征的维数。通常,rank 选
的越小,意味着模型的新参数越少,从而降低开销。然后,秩越低,LoRA 层捕捉到的表示能力
就越低,这可能会影响模型在新任务重的表现。

“补偿不同秩 $r$ 带来的影响。经验发现,当 $r$ 增加时,$UV$ 的范数(magnitude)往往也会增加。”

这句话包含两个部分:

  1. “当 $r$ 增加时,$UV$ 的范数(magnitude)往往也会增加。”
  2. “补偿不同秩 $r$ 带来的影响。”

我们先解释第一部分。

1. 当 $r$ 增加时,$UV$ 的范数(magnitude)往往也会增加。

  • $UV$ 是什么? 在 LoRA 中,$UV$ 是我们希望添加到原始权重矩阵 $W_0$ 上的低秩更新矩阵 $\Delta W$。它的维度是 $d \times k$,与 $W_0$ 相同。
  • 范数 (Norm) 或 Magnitude 是什么? 范数是衡量一个向量或矩阵“大小”或“强度”的数学概念。对于矩阵来说,常见的范数有 Frobenius 范数(所有元素的平方和的平方根)或谱范数(最大奇异值)。简单来说,一个矩阵的范数越大,意味着它的元素值整体上越大,或者它代表的线性变换“强度”越大。
  • 为什么 $UV$ 的范数会随 $r$ 增加而增加?
    • 回想一下矩阵乘法 $UV$ 的另一种表示方式:$UV = \sum_{i=1}^r u_i v_i^T$,其中 $u_i$ 是矩阵 $U$ 的第 $i$ 列向量,$v_i^T$ 是矩阵 $V$ 的第 $i$ 行向量。每个 $u_i v_i^T$ 都是一个秩为 1 的矩阵(如果 $u_i$ 和 $v_i$ 都是非零向量)。$UV$ 是 $r$ 个这样的秩 1 矩阵的和。
    • 在 LoRA 训练过程中,$U$ 和 $V$ 的参数是通过优化算法(如梯度下降)学习得到的,目的是最小化下游任务的损失函数。
    • 当 $r$ 较小时,我们只有较少的“自由度”(即较少的 $u_i, v_i$ 对)来构建更新矩阵 $\Delta W = UV$。这限制了 $\Delta W$ 能够表示的复杂度和“大小”。
    • 当 $r$ 增加时,我们有了更多的“自由度”或“通道”来构建 $\Delta W$。优化算法可以利用这些额外的自由度来找到一个更能显著降低损失的 $\Delta W$。
    • 经验上发现,为了更有效地降低损失,优化器倾向于学习到 $U$ 和 $V$ 的参数,使得它们相乘得到的 $UV$ 矩阵具有更大的范数。这就像有了更多的工具或材料,你更容易构建一个更大、更复杂的结构。
    • 虽然这不是一个严格的数学证明,但在实际训练中,随着 $r$ 的增加,模型有更大的能力去修改原始权重 $W_0$,而这种更大的修改能力通常体现在 $UV$ 矩阵具有更大的范数。

简单来说: 增加秩 $r$ 给了 LoRA 模型更大的参数空间和表达能力来学习任务特定的权重更新。在优化过程中,这种更大的能力往往被用来产生一个“幅度”更大(范数更大)的更新矩阵 $UV$,以便更好地适应新任务。

2. “补偿不同秩 $r$ 带来的影响。”

  • 影响是什么? 如果我们直接将 $UV$ 加到 $W_0$ 上(即 $W = W_0 + UV$),那么当 $r$ 增加时,$UV$ 的范数会增加,这意味着对原始权重 $W_0$ 的更新幅度会更大。
  • 问题: 这种随 $r$ 变化的更新幅度可能会导致训练不稳定。例如,如果学习率是为较低的 $r$ 调优的,那么在较高的 $r$ 下,由于更新幅度过大,可能会导致训练发散或性能下降。反之,如果学习率是为较高的 $r$ 调优的,那么在较低的 $r$ 下,更新幅度可能太小,导致训练缓慢或收敛不佳。
  • 补偿方法: LoRA 引入了缩放因子 $\frac{\alpha}{r}$。实际的权重更新是 $\frac{\alpha}{r} UV$。
    • 这里的除以 $r$ 就是为了抵消前面提到的 $UV$ 范数随 $r$ 增加而增加的趋势。它试图将不同 $r$ 值下 $UV$ 的“贡献”标准化。
    • 乘以 $\alpha$ 则提供了一个额外的控制旋钮,用于调整整体的缩放比例。通常,$\alpha$ 被设置为一个固定的值(例如 16 或 32),或者直接设置为等于 $r$。当 $\alpha = r$ 时,缩放因子就是 1,即 $W = W_0 + UV$。但 LoRA 的作者发现,即使 $\alpha$ 不等于 $r$,使用 $\frac{\alpha}{r}$ 这种形式的缩放仍然有助于稳定训练。
  • 作用: 通过使用 $\frac{\alpha}{r}$ 作为缩放因子,LoRA 使得实际应用到 $W_0$ 上的更新 $\frac{\alpha}{r} UV$ 的范数在不同 $r$ 值下更加一致。这使得 LoRA 训练对于 $r$ 的选择更加鲁棒,减少了在改变 $r$ 时需要重新调优学习率等其他超参数的工作量。你可以更容易地尝试不同的 $r$ 值,而无需担心训练过程变得不稳定。

最终总结:

LoRA 中的秩 $r$ 决定了模型学习任务特定信息的容量。经验发现,容量越大($r$ 越高),学习到的更新矩阵 $UV$ 的“大小”(范数)也越大。为了避免这种随 $r$ 变化的更新幅度影响训练的稳定性和超参数的选择,LoRA 引入了 $\frac{\alpha}{r}$ 的缩放因子。其中的除以 $r$ 项专门用于补偿 $UV$ 范数随 $r$ 增加而增加的趋势,使得实际的权重更新幅度在不同 $r$ 值下更加一致,从而简化了超参数调优,提高了训练的鲁棒性。alpha 是 LoRA 中的一个缩放系数,它用于调节 LoRA 层的输出对最终模型的影响。在
LoRA 中,LoRA 层的输出会被乘以一个缩放系数 alpha / rank
。其计算公式为:
$$
HLoRA =
alpha
/rank × (A × B)
$$

其中 HLoRA 是 LoRA 层的输出。alpha 的作用是控制 LoRA 层在微调过程中对整个模型
输出的影响:
• 当 alpha 较大, LoRA 层对模型的影响会加大,LoRA 层的输出将更强烈地改变模型的行
为。这通常在微调任务需要对模型进行显著调整时使用。
• 当 alpha 较小, LoRA 层对模型的影响较小,意味着微调过程中 LoRA 对模型的改动相对
温和。这通常适用于希望仅对模型进行小幅调整的任务。
因此,alpha 是用来平衡 LoRA 层与原始模型输出的系数,确保 LoRA 层的更新不会过度影响模型的输出,保持模型稳定性

4.LoRA模型如何初始化

在 LORA 模型中,down 层的权重使用标准正态分布初始化,标准差为 1/rank,而 up 层的权重初始化为 0。原因如下:

  • 保持模型的初始输出不变:在模型初始化时,不希望 LoRA 层对模型的输出产生大的改变或干扰。将 up 层的权重初始化为零,意味着 LoRA 层在刚开始时对模型的输出不会有任何影响,模型的行为与没有 LoRA 的原始模型一致。
  • 渐进式学习 LoRA 参数:LoRA 层的目标是在不破坏模型预训练的情况下,逐步微调这些新插入的层。因此,在开始时,将 up 层初始化为零可以保证 LoRA 仅从预训练的基础模型输出开始,逐步加入对任务相关的影响。down 层的初始化使用正态分布,它的作用是将输入特征进行压缩,这个压缩操作开始时可以是任意的(因此使用正态分布初始化)。但up层的作用是将低秩表示映射回到高维度,如果 up 层初始化为零,则不会在初始时对原始模型的输出产生干扰。这样,网络可以逐渐通过学习适应新的任务,而不是一开始就对模型施加随机的影响。

5.介绍一下大模型常用微调方法LORA和Ptuning的原理。

Lora方法的核心是在大型语言模型上对指定参数增加额外的低秩矩阵,也就是在原始PLM旁边增加一个旁路,做一个降维再升维的操作。并在模型训练过程中,固定PLM的参数,只训练降维矩阵A与升维矩阵B。

Ptuning方法的核心是使用可微的virtual token替换了原来的discrete tokens,且仅加入到输入层,并使用prompt encoder(BiLSTM+MLP)对virtual token进行编码学习。

以下是对大模型常用微调方法LORA和P-tuning原理的介绍:

LORA原理

  • 核心思想:LORA即低秩适应(Low-Rank Adaptation),其核心是在模型的权重矩阵中引入两个低秩矩阵的乘积来近似全参数微调的更新,从而大幅减少参数量,降低计算和存储成本.
  • 在LLM中的应用
    • 参数效率:通过低秩适应减少了参数更新的数量,使得在有限的计算资源下可以对更大的模型进行微调。
    • 微调速度:参数量的减少加快了微调过程,让模型能更快适应特定任务。
    • 多任务学习:允许在同一预训练模型基础上,通过不同的低秩矩阵适应不同的下游任务,实现多任务学习。
    • 推理效率:在推理阶段,将训练好的低秩矩阵与预训练权重合并,避免了推理时的额外开销。

P-tuning原理

  • 核心思想:P-tuning是通过修改输入提示(prompt)来调整模型对特定任务的适应性,而不是直接调整模型的权重,主要是将 prompts 转化为可学习的嵌入层,并使用多层感知机(MLP)和长短期记忆网络(LSTM)等对其进行处理,从而找到更优的提示表示,以更好地引导模型生成或预测任务相关的输出.

    P-Tuning 是一种参数高效微调 (PEFT) 方法,它的核心思想是冻结大型预训练语言模型 (PLM) 的参数,并通过优化一小部分连续的、可训练的“提示”(Prompt)参数来适应下游任务。

    与传统的“硬提示”(手动设计的文本前缀)不同,P-Tuning 使用的是软提示(Soft Prompt),这些软提示不是由实际的词汇 token 组成的,而是由一系列可训练的连续向量组成的。

    以下是 P-Tuning 的详细过程:

    1. 选择并冻结预训练模型 (Select and Freeze the Pre-trained Model):

      • 首先,选择一个已经在大规模语料库上完成预训练的语言模型(例如 BERT, GPT, T5 等)。
      • 在 P-Tuning 过程中,这个预训练模型的所有原始参数(包括 Transformer 层、词嵌入层等)都将被冻结,不会在微调过程中更新。
    2. 构建任务模板 (Construct Task Template):

      • 为下游任务设计一个模板,将原始输入数据转化为一个带有“槽位”(slot)的句子或短语。这个模板通常包含原始输入和用于引导模型输出的特殊标记或位置。
      • 例如,对于情感分类任务,输入是评论文本 “这部电影太棒了!”。模板可能是 “[Prompt] 这部电影太棒了!感觉是 [Mask]”。
      • 这里的 “[Prompt]” 和 “[Mask]” 是模板中的特殊部分。
    3. 引入连续的软提示 (Introduce Continuous Soft Prompts):

      • P-Tuning 的关键步骤。在将输入文本送入模型之前,不是在文本层面添加硬提示词,而是在模型的输入嵌入层中进行操作。
      • 在输入文本的嵌入向量序列前或中间,插入一系列可训练的连续向量。这些向量就是软提示的参数。
      • 假设原始输入序列的嵌入是 $E_{input} = {e_1, e_2, …, e_n}$。
      • P-Tuning 引入一个由 $m$ 个向量组成的软提示序列 $P = {p_1, p_2, …, p_m}$,其中每个 $p_i$ 是一个与模型词嵌入维度相同的向量,且这些向量的参数是随机初始化可训练的。
      • 将软提示向量序列与输入嵌入序列拼接起来,形成新的输入序列嵌入 $E’_{input} = {p_1, …, p_m, e_1, …, e_n}$(或者根据模板插入到其他位置)。
    4. 通过冻结的 PLM 进行前向传播 (Forward Pass through the Frozen PLM):

      • 将拼接好的输入嵌入序列 $E’_{input}$ 送入冻结的预训练语言模型。
      • 模型按照正常的流程进行计算,通过各个 Transformer 层,生成每个位置的上下文相关的输出表示(隐藏状态)。
    5. 定义 Verbalizer (Mapping Function):

      • 由于 PLM 的输出通常是词汇的概率分布,而下游任务的输出可能是类别标签(如“积极”、“消极”)或特定的值。需要一个机制将模型的输出映射到任务所需的格式。这个机制称为 Verbalizer
      • 对于分类任务,Verbalizer 将模型在模板中特定位置(例如 “[Mask]” 标记对应的位置)的输出表示,映射到任务的类别。这通常通过定义一个词汇到标签的映射来实现。例如,将模型预测到词汇 “很棒” 的概率映射到“积极”类别,将预测到词汇 “糟糕” 的概率映射到“消极”类别。训练目标就是最大化正确标签对应词汇的概率。
    6. 计算任务损失 (Calculate Task Loss):

      • 根据 Verbalizer 的输出和数据的真实标签,计算任务的损失函数(例如,分类任务的交叉熵损失)。
    7. 反向传播和参数更新 (Backpropagation and Parameter Update):

      • 计算损失函数相对于模型参数的梯度。
      • 关键点: 由于 PLM 的参数是冻结的,梯度会反向传播到软提示的参数(即向量 $p_1, …, p_m$ 的值)。
      • 使用优化器(如 Adam)根据计算出的梯度更新软提示向量的参数。
    8. 可选:使用 Prompt Encoder (Optional: Use a Prompt Encoder):

      • 为了提高训练的稳定性和性能,P-Tuning v1 提出可以使用一个小的神经网络(如一个浅层 MLP 或 LSTM)作为 Prompt Encoder
      • 在这种情况下,我们不是直接训练软提示向量 $p_1, …, p_m$,而是训练 Prompt Encoder 的参数。Prompt Encoder 接收一个更小的初始参数集作为输入,然后生成软提示向量序列 $P = {p_1, …, p_m}$。
      • 训练时,梯度会反向传播到 Prompt Encoder 的参数,而不是直接到 $p_i$ 的值。

    总结 P-Tuning 的过程:

    1. 冻结大型预训练模型的参数。
    2. 为下游任务设计一个包含软提示槽位的模板。
    3. 在模型的输入嵌入层,用一系列可训练的连续向量(软提示)替换模板中的软提示槽位。
    4. 将包含软提示嵌入的序列输入到冻结的 PLM 中进行前向计算。
    5. 使用 Verbalizer 将 PLM 在特定位置的输出映射到任务所需的格式(如类别标签)。
    6. 计算任务损失。
    7. 反向传播损失,仅更新软提示向量的参数(或生成软提示的 Prompt Encoder 的参数)。
    8. 重复步骤 4-7,直到模型在下游任务上收敛。

    通过这个过程,P-Tuning 能够在只训练极少量参数的情况下,有效地引导大型冻结模型完成各种下游任务,显著降低了微调的计算和存储成本。

  • 优点及发展:P-tuning通常比传统权重微调更简单,无需调整模型权重,只需设计不同提示,更容易实现和实验,还可通过设计更多提示提高模型性能,且具有较好的泛化能力。不过,其成功很大程度上取决于提示的设计,需要深入理解任务和语言模型工作原理并进行大量实验调整。为改进P-tuning等方法存在的问题,如缺乏模型参数规模和任务通用性、缺少深度提示优化等,又发展出了P-tuning v2,它在不同模型规模和各种困难的自然语言理解任务上表现与微调相匹配,且大大降低了训练时间、内存消耗和存储成本.

6.训练技巧

1.怎么解决训练使用float16导致溢出的问题

在训练过程中使用 float16 可能会导致数值溢出(overflow)或下溢(underflow),特别是在处理大模型和高动态范围的数据时。为了解决这些问题,可以采取以下几种策略:

1. 损失缩放(Loss Scaling)

损失缩放是一种常见的方法,用于在使用 float16 进行训练时保持数值稳定性。具体步骤如下:

  • 在前向传播过程中,将损失值乘以一个缩放因子(例如 1024 或 65536)。

  • 在反向传播计算梯度时,将梯度除以同样的缩放因子。

通过这种方式,可以在梯度计算中保持足够的数值范围,减少下溢和溢出的风险。

1
2
3
4
5
6
loss_scale = 1024.0
scaled_loss = loss * loss_scale
scaled_loss.backward()
for param in model.parameters():
if param.grad is not None:
param.grad.data /= loss_scale

2. 混合精度训练(Mixed Precision Training)

混合精度训练结合使用 float16 和 float32,以兼顾计算效率和数值稳定性。通常使用 NVIDIA 的 Apex 或 PyTorch 的 AMP(Automatic Mixed Precision)工具来实现混合精度训练。

在 PyTorch 中,可以使用 torch.cuda.amp 进行混合精度训练:

1
2
3
4
5
6
7
8
9
10
from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()
for input, target in data_loader:
optimizer.zero_grad()
with autocast():
output = model(input)
loss = criterion(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

3. 模型架构调整

对于特定的模型,可以通过调整架构来减少溢出和下溢问题。例如:

  • 在网络中添加适当的正则化层(如 Batch Normalization)来稳定训练。
  • 使用更小的初始学习率,并逐渐增加。
  • 采用较浅的网络层,减少过深网络带来的数值不稳定性。

4. 动态损失缩放

动态损失缩放是一种自适应调整损失缩放因子的方法,能够根据训练过程中的数值范围动态调整缩放因子。可以通过 NVIDIA Apex 工具中的 DynamicLossScaler 类来实现。

1
2
3
4
5
6
7
from apex import amp
model, optimizer = amp.initialize(model, optimizer, opt_level="O1")
for input, target in data_loader:
optimizer.zero_grad()
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
optimizer.step()

5. 监控和调整训练参数

在训练过程中,持续监控梯度和权重的数值范围,及时调整训练参数(如学习率、损失缩放因子等)以避免数值问题。可以使用工具(如 TensorBoard)来可视化这些信息,并做出相应的调整。

总结

通过结合使用损失缩放、混合精度训练、模型架构调整和动态损失缩放等方法,可以有效解决 float16 训练过程中出现的数值溢出问题,提高训练效率和数值稳定性。

2.llm训练的时候用float16,还是bfloat16,float32?

在训练大规模语言模型(LLM)时,使用不同的数值精度(如float16、bfloat16、float32)各有其优缺点,具体选择取决于训练的需求、硬件支持和精度要求。以下是每种数值精度的特点和适用情况:

  1. Float32(单精度浮点数)

    • 特点:32位浮点数,提供较高的精度和动态范围。

    • 优点:高精度,数值稳定性好,广泛支持。

    • 缺点:内存和计算需求较大,训练速度较慢。

    • 适用情况:用于需要高精度的训练场景,尤其是在数值稳定性非常关键的情况下。

  2. Float16(半精度浮点数)

    • 特点:16位浮点数,精度和动态范围较低。

    • 优点:内存和计算需求低,能够显著加速训练过程,适合在支持混合精度训练的硬件(如NVIDIA的Tensor Cores)上使用。

    • 缺点:数值稳定性较差,容易出现溢出和下溢,需要额外的措施(如损失缩放)来确保训练稳定性。

    • 适用情况:适用于资源受限、需要加速训练的场景,但需要注意数值稳定性。

  3. BFloat16(单精度浮点数的变种)

    • 特点:16位浮点数,与float16相比,具有更大的动态范围,但精度略低。

    • 优点:较大的动态范围使其在处理大模型和复杂计算时更加稳定,不需要复杂的损失缩放技术。兼顾计算效率和数值稳定性。

    • 缺点:精度不如float32,但在大多数情况下足够使用。

    • 适用情况:适用于需要平衡训练速度和数值稳定性的场景,尤其在支持bfloat16的硬件(如Google的TPUs)上效果更佳。

总结:

  • Float32:适用于需要高精度和数值稳定性的训练任务。

  • Float16:适用于希望加速训练过程并能应对数值稳定性挑战的任务,适合使用NVIDIA GPU的混合精度训练。

  • BFloat16:适用于需要兼顾训练速度和数值稳定性的任务,尤其在使用Google TPU时。
    在实际操作中,bfloat16和混合精度(float16和float32结合使用)往往是大模型训练中的最佳选择,因其在计算效率和数值稳定性之间达到了较好的平衡。

3.float16是什么数据类型?

基本概念

简要介绍

半精度浮点数,又称float16,FP16,half-precision floating-point,binary16,用16位二进制来表示数据的浮点数。

格式详解

Sign(符号位):1位。0表示正数,1表示负数。

Exponent(指数位):5位。对于float16,范围为00001~11110,偏置常数为15,因此可以表示的数据范围为 $2^{1−15}$ ~ $2^{30−15}$ 。

Fraction(尾数位):10位。为了确保浮点数的表示唯一性,所有浮点数格式规定尾数部分的高位始终为1,这一位在表示中是隐含的,因此尾数实际上有11位的精度。

因此,float16的计算公式如下(同样也可以采用 $1 \times 2^0 + 0(1) \times 2^1 …$ 的方式对尾数位进行计算):

$$ (-1)^{sign} \times 2^{exponent-15} \times (1+ \frac{fraction_{2进制到10进制}}{1024})$$

特殊数值

最大正数(65504): $0 , 11110 , 1111111111$

最大负数(-65504): $1 , 11110 , 1111111111$

+0:$0 , 00000 , 0000000000$

-0:$1 , 00000 , 0000000000$

正无穷:$0 , 11111 , 0000000000$

负无穷:$1 , 11111 , 0000000000$(

分辨率和最小分辨率

分辨率:在当前的指数位设置下,每个最小单位(1/1024)所代表的数值大小,即 $2^{exponent-15} \times \frac{1}{1024}$ 。因此,不同分辨率的数据相加可能会由于精度丢失而导致小数部分被舍去。

最小分辨率:表示float16,可以表示的最小精度,即 $2^{-14} \times \frac{1}{1024} \approx 0.000000059604645$

4.bfloat16和float32是什么数据类型?

bfloat16

bfloat16,又称BP16,brain floating point,用16位二进制来表示数据的浮点数。相较于float16,其格式是Sign(符号位)为1位;Exponent(指数位)为8位,偏置常数为127;Fraction(位数)为7位。其计算公式,特殊数值,分辨率和最小分辨率的表示方法,同float16。

float32

单精度浮点数,又称float32,FP32,single-precision floating-point,binary32,用32位二进制来表示数据的浮点数。相较于float16,其格式是Sign(符号位)为1位;Exponent(指数位)为8位,偏置常数为127;Fraction(位数)为23位。其计算公式,特殊数值,分辨率和最小分辨率的表示方法,同float16。

5.什么是FP8?

基本概念

FP8是FP16的衍生产物,一般包含1E5M2和1E3M3两种编码格式。其中,E表示指数位,M表示尾数位(也可以用fraction)。

表示范围

FP8也采用浮点表示法(符号位:0正1负;指数位和尾数位共同表示数值大小)。详细请参照FP16。

差异:E5M3严格遵守IEEE754标准(指数位全为1,尾数位任意,则表示无穷)。但E4M3不完全遵守,指数位全为1,也需按照浮点数表示法进行有效数字的换算;当且仅当指数和位数全为1时,才表示无穷。

与INT8比较

  • FP8 具有更大的表示范围;但在一定范围内,其表示精度相较 INT8 更差。

  • 选择合适的缩放因子时,INT8的量化精度高于FP8,且两者之间的误差几乎相差一个数量级。FP8将提供更好的宽容性,对scale的选择不敏感。