[译] Unsloth - LoRA 超参数指南

原文:LoRA Hyperparameters Guide

LoRA 超参数是可调节的参数,用于控制低秩适应(LoRA)对大型语言模型(LLMs)进行微调的方式。由于存在诸多选项(如学习率和训练轮次等)以及数百万种可能的组合,因此选择合适的参数值至关重要,这有助于在微调过程中实现更高的准确性、稳定性和质量,并减少幻觉现象的出现。

你将基于数百篇研究论文和实验的洞见,学习这些参数的最佳实践,并了解它们如何对模型产生影响。尽管我们建议使用 Unsloth 的默认设置,但理解这些概念将使你能够完全掌控微调过程。

目标是通过调整超参数数值来提升准确性,同时抑制过拟合或欠拟合问题。过拟合是指模型过度记忆训练数据,从而削弱其对未见过的新输入的泛化能力。我们的目标是让模型具备良好的泛化能力,而非仅仅机械记忆数据。

关键微调超参数

学习率(Learning Rate)

定义了在每个训练步骤中模型权重的调整幅度。

  • 更高的学习率:会使初始收敛速度更快,但如果设置得过高,可能导致训练变得不稳定,或无法找到最优的最小值。
  • 更低的学习率:会使训练过程更加稳定和精确,但可能需要更多的训练轮次才能收敛,从而增加整体训练时间。尽管人们通常认为低学习率会导致欠拟合,但实际上它也可能导致过拟合,甚至会阻碍模型的学习能力。
  • 典型范围2e-4(0.0002)至 5e-6(0.000005)。
    • 对于常规的 LoRA/QLoRA 微调,我们建议将 2e-4 作为起始学习率。
    • 对于强化学习(如 DPO、GRPO 等),我们建议使用 5e-6 的学习率。
    • 对于全量微调,通常较低的学习率更为合适。

轮次(Epochs)

模型完整遍历训练数据集的次数。

  • 更多的训练轮次:有助于模型更好地学习,但轮次过多会导致模型记住训练数据,损害其在新任务上的表现。
  • 更少的训练轮次:可缩短训练时间并防止过拟合,但如果轮次不足,模型可能无法学习到数据集的潜在模式,从而导致训练不充分。
  • 建议:1-3 轮次。对于大多数基于指令的数据集,训练超过 3 轮次的收益会逐渐递减,并会增加过拟合的风险。

LoRA 或 QLoRA

LoRA 使用 16 位精度,而 QLoRA 是一种 4 位微调方法。

  • LoRA:16 位微调方法。其速度略快且精度略高,但显存消耗显著更多(是 QLoRA 的 4 倍)。建议在 16 位环境以及需要最高精度的场景中使用。
  • QLoRA:4 位微调方法。速度略慢且精度稍低,但显存占用量大幅减少(仅为前者的 1/4)。
    • 使用 Unsloth 中的 QLoRA 技术,700 亿参数的 LLaMA 模型可在小于 48GB 的显存中运行。更多细节在这里

超参数和建议

超参数 功能 推荐设置
LoRA Rank (r) 控制 LoRA 适配器矩阵中可训练参数的数量。秩越高,模型容量越大,但内存占用也会增加。 8, 16, 32, 64, 128
选择 16 或 32。
LoRA Alpha
(lora_alpha)
缩放与秩(r)相关的微调调整强度。 r (标准)或 r * 2 (常见启发式方法)
LoRA Dropout 一种正则化技术,在训练过程中随机将一部分 LoRA 激活值设为零,以防止过拟合。该技术作用有限,因此我们默认将其设为 0。 0 (默认)到 0.1
Weight Decay 一种正则化项,通过惩罚较大的权重来防止过拟合并提高泛化能力。注意不要设置过大的数值! 0.01 (推荐)到 0.1
Warmup Steps 在训练开始时逐步提高学习率。 总步数的 5-10%
Scheduler Type 在训练过程中动态调整学习率。 linearcosine
Seed (random_state) 为确保结果的可重复性而设置的固定数值。 任意整数(例如,423407
Target Modules 你希望将 LoRA 适配器应用于模型的哪个部分 - 注意力层或多层感知机,或两者皆可。
注意力层:q_proj, k_proj, v_proj, o_proj
MLP: gate_proj, up_proj, down_proj
建议所有的线性层:q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj

梯度累积步数与批量大小的等效性

有效批量大小

正确配置批量大小对于在训练稳定性和 GPU 显存限制之间取得平衡至关重要。这由两个参数控制,它们的乘积即为有效批量大小

有效批量大小 = batch_size * gradient_accumulation_steps

  • 更大的有效批量大小通常会使训练过程更平滑、更稳定。
  • 较小的有效批量大小可能会引入更多的随机性。

尽管每个任务各不相同,但以下配置为实现稳定的 16 有效批量大小提供了一个很好的起点,该配置适用于现代 GPU 上的大多数微调任务。

参数 描述 推荐设置
批量大小(batch_size 在单个 GPU 上单次前向/反向传播过程中处理的样本数量。
显存占用的主要因素。更高的值可以提高硬件利用率并加快训练速度,但前提是它们能在显存中放得下。
2
梯度累积
gradient_accumulation_steps
执行单次模型权重更新前需要处理的小批量数量。
训练时间的主要决定因素。通过模拟更大的批量大小来节省显存。数值越高,每个训练轮次的时间越长。
8
有效批量大小(计算得出) 每次梯度更新所使用的真实批量大小。它直接影响训练的稳定性、质量以及最终的模型性能。 4 到 16 推荐:16(2*8)

显存与性能的权衡

假设你希望每个训练步骤处理 32 个数据样本,那么你可以执行以下操作:

  • batch_size = 32, gradient_accumulation_steps = 1
  • batch_size = 16, gradient_accumulation_steps = 2
  • batch_size = 8, gradient_accumulation_steps = 4
  • batch_size = 4, gradient_accumulation_steps = 8
  • batch_size = 2, gradient_accumulation_steps = 16
  • batch_size = 1, gradient_accumulation_steps = 32

尽管所有这些配置在模型权重更新方面是等效的,但它们对硬件的要求却大相径庭。

第一种配置(batch_size = 32)会消耗最多的显存,且在大多数 GPU 上可能无法运行。最后一种配置(batch_size = 1显存消耗最少,但代价是训练速度会略微变慢。为避免出现 OOM(内存不足)错误,建议始终设置较小的 batch_size,并通过增加 gradient_accumulation_steps 来达到你目标的有效批量大小

Unsloth 梯度累积修复

由于我们对梯度累积的错误修复,在 Unsloth 中梯度累积和批量大小现已完全等效。我们针对梯度累积实施了特定的错误修复,解决了一个常见问题,即这两种方法此前无法产生相同的结果。这曾是业界广泛面临的挑战,但对于 Unsloth 用户而言,现在这两种方法可互换使用。

欲了解更多详情,请阅读我们的博客文章

在我们进行修复之前,即使 batch_sizegradient_accumulation_steps 的组合得到相同的有效批量大小(即 batch_size × gradient_accumulation_steps = 16),训练表现也并不等效。例如,像 b1/g16b2/g8b4/g4b8/g2b16/g1 这样的配置,有效批量大小均为 16,但如图表所示,使用标准梯度累积时,损失曲线并不一致:

Before - Standard gradient accumulation

应用我们的修复后,无论以何种方式实现 16 的有效批量大小,损失曲线现在都能正确对齐:

After - Unsloth gradient accumulation

Unsloth 中的 LoRA 超参数

以下展示了一个标准配置。尽管 Unsloth 提供了优化的默认设置,但理解这些参数是手动调优的关键。

Standard Configuration

  1. r = 16, # Choose any number > 0 ! Suggested 8, 16, 32, 64, 128:微调过程的秩(r)。较大的秩会占用更多内存且运行更慢,但可提高复杂任务的准确性。我们建议秩取值如 8 或 16(用于快速微调),最大可达 128。秩设置过大可能导致过拟合,损害模型质量。
  2. target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj",],:为了获得最佳性能,应将 LoRA 应用于所有线性层研究表明,针对所有层进行优化对于匹配全量微调的性能至关重要。尽管可以通过移除模块来减少内存占用,但我们强烈建议不要这样做,以确保模型质量最大化。
  3. lora_alpha = 16,:一个控制微调调整强度的缩放因子。将其设置为与秩(r)相等是一个可靠的基准。一种流行且有效的启发式方法是将其设置为秩的两倍(r * 2),这通过给 LoRA 更新赋予更多权重,使模型学习得更积极。
  4. lora_dropout = 0, # Supports any, but = 0 is optimized:一种正则化技术,通过在每个训练步骤中随机将一部分 LoRA 激活值设为零来帮助防止过拟合近期研究表明,对于微调中常见的短训练周期,lora_dropout 可能是一种不可靠的正则化方法。lora_dropout = 0 时,Unsloth 的内部代码可优化训练过程,使其速度略有提升。但如果您怀疑模型存在过拟合问题,我们建议设置非零值。
  5. bias = "none", # Supports any, but = "none" is optimized:为加快训练速度并减少内存占用,请将此设置保留为 “none”。该设置会避免训练线性层中的偏置项,因为这些偏置项虽会增加可训练参数,但几乎不会带来实际收益。
  6. use_gradient_checkpointing = "unsloth", # True or "unsloth" for very long context:选项包括 TrueFalse"unsloth"。我们推荐使用 "unsloth",因为它能额外减少 30% 的内存占用,并支持极长上下文的微调。您可以在我们关于长上下文训练的博客文章中了解更多信息。
  7. random_state = 3407,:用于确保运行具有确定性和可重复性的随机种子。由于训练过程涉及随机数,因此设置固定的种子对于保证实验结果的一致性至关重要。
  8. use_rslora = False, # We support rank stabilized LoRA:一项实现了 Rank-Stabilized LoRA 的高级功能。如果设置为 True,有效缩放将变为 $\frac{\text{lora_alpha}}{\sqrt{r}}$,而非标准的 $\frac{\text{lora_alpha}}{r}$。这有时能提高稳定性,尤其是在较高秩的情况下。
  9. loftq_config = None, # And LoftQ:这是一种 LoftQ 所提出的高级技术,它利用预训练权重中的前 ‘r’ 个奇异向量来初始化 LoRA 矩阵。这一方法能够提升模型精度,但在训练开始时可能会导致显著的内存激增。

LoRA Target Modules 和 QLoRA vs LoRA

使用 target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj",] 来同时针对 MLP 和注意力层,以提高准确性。

QLoRA 采用 4 位精度,可将显存占用减少 75% 以上。

LoRA(16位)精度略高,速度也更快。

根据实证实验和原始 QLoRA 论文等研究文献,将 LoRA 同时应用于注意力层和 MLP 层效果最佳。

RougeL Score

上图展示了不同目标模块的 RougeL 分数(分数越高越好),以及 LoRA 与 QLoRA 的对比情况。

前 3 个点展示了:

  1. QLoRA-All:在所有前馈神经网络(FFN)/多层感知机(MLP)和注意力层上应用 LoRA。这是最佳方案
  2. QLoRA-FFN:仅在 FFN(前馈神经网络)上应用 LoRA。相当于针对 gate_projup_projdown_proj 这些模块。
  3. QLoRA-attention:仅在注意力层上应用 LoRA。相当于针对 q_projk_projv_projo_proj 这些模块。

仅针对补全内容进行训练,将输入内容屏蔽

QLoRA 论文表明,屏蔽补全,输出,或 assistant 消息可进一步将准确率提升几个百分点(1%)。以下展示了 Unsloth 内部的实现示例。

method

QLoRA 论文指出,仅针对补全内容进行训练能显著提升模型准确率,尤其是在多轮对话式微调任务中!我们在对话相关的 notebook 中采用了此方法。

MMLU results

在 Unsloth 中启用针对补全内容的训练时,需要自行确定 instruction 和 assistant 部分。我们计划在未来实现这一过程的自动化!

对于 Llama 3、3.1、3.2、3.3 和 4 模型,你可以按如下方式定义这些部分:

1
2
3
4
5
6
7
from unsloth.chat_templates import train_on_responses_only

trainer = train_on_responses_only(
trainer,
instruction_part = "<|start_header_id|>user<|end_header_id|>\n\n",
response_part = "<|start_header_id|>assistant<|end_header_id|>\n\n",
)

对于 Gemma 2、3、3n 模型,你可以按如下方式定义这些部分:

1
2
3
4
5
6
7
from unsloth.chat_templates import train_on_responses_only

trainer = train_on_responses_only(
trainer,
instruction_part = "<start_of_turn>user\n",
response_part = "<start_of_turn>model\n",
)

避免过拟合和欠拟合

过拟合(泛化能力差/过度专门化)

模型记住了训练数据(包括其中的统计噪声),因此无法对未见过的数据进行泛化。

如果训练损失降至 0.2 以下,模型很可能已过拟合 - 此时模型在未见过的任务上表现往往不佳。

一个简单技巧是 LoRA alpha 缩放:直接将每个 LoRA 矩阵的 alpha 值乘以 50%,这会减弱微调的影响。

这与权重合并/平均相关

你可以获取原始基础(或指令)模型,添加 LoRA 权重,然后将结果除以 2。这将得到一个平均模型 — 其功能等效于将 alpha 值减半。

解决方案:

  • 调整学习率:较高的学习率常导致过拟合,尤其是在短周期训练中。对于长周期训练,较高学习率可能效果更好,建议通过实验对比效果。
  • 减少训练轮次(epochs):在 1、2 或 3 轮后停止训练。
  • 增加 weight_decay初始值可设为 0.01 或 0.1。
  • 增大 lora_dropout使用 0.1 等数值增强正则化效果。
  • 增大批量大小或梯度累积步数
  • 扩展数据集:将开源数据集与自有数据合并或拼接,优先选择高质量数据。
  • 基于评估早停训练:启用评估机制,当评估损失连续几步上升时终止训练。
  • LoRA Alpha 缩放:训练后及推理阶段缩小 alpha 值,减弱微调影响。
  • 权重平均:将原始指令模型与微调模型的权重相加后除以 2。

欠拟合(过于泛化)

模型无法捕捉训练数据中的潜在模式,通常是由于模型复杂度不足或训练时长不够。

解决方案:

  • 调整学习率:若当前学习率过低,提高学习率可能加速收敛(尤其在短周期训练中);长周期训练可尝试降低学习率,通过测试确定最优方案。
  • 增加训练轮次(epochs):延长训练周期,但需监控验证损失以避免过拟合。
  • 增大 LoRA 秩(r)和 alpha 值:秩应至少与 alpha 值相等,小模型或复杂数据集需更大的秩(通常在 4 到 64 之间)。
  • 使用更贴合领域的数据集:确保训练数据高质量且与目标任务直接相关。
  • 将批量大小(batch size)降至 1:这会使模型的更新更加激进。

微调没有单一的“最佳”方法,只有最佳实践。实验是找到适合您特定需求的关键。我们的笔记本基于多篇论文研究和我们的实验自动设置最佳参数,为您提供一个很好的起点。祝微调顺利!

致谢:非常感谢 Eyera 对本指南的贡献!