DeepSeek 大模型微调理论详解与参数配置
在大模型的微调过程中,**LoRA(低秩适配)**参数设置是提升训练效率和性能的关键。通过减少需更新的参数量,LoRA 能够在维持模型性能的同时显著降低计算成本。
然而,LoRA 并非唯一影响训练效果的因素。诸如学习率、批次大小以及优化器(如 AdamW)等参数同样在微调过程中起着至关重要的作用。
学习率决定了模型每次更新的幅度,批次大小则影响了每次训练中样本的处理量,而优化器则确保模型参数的平稳更新。了解并灵活调整这些训练参数,不仅能帮助你在微调过程中得心应手,更能快速提升训练效果。
本文将深入探讨多轮对话数据集的微调方法,详细解析 LoRA 参数原理及训练超参数的配置策略。
1. 多轮对话数据构建
多轮对话微调其实和单轮对话(或者说指令数据)差不多,在我看来其实类似于多个指令数据的组合。单轮对话数据处理的时候只需要处理输入和输出即可,训练的时候输入置为 -100,输出不变,而多轮对话微调数据集以及标签的构造方式则更为复杂。
1.1 训练不充分方案
第一种方法是,只把最后一轮机器人的回复作为要学习的标签,其它地方作为语言模型概率预测的 condition,无需学习,赋值为 -100,忽略这些地方的 loss。
inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
labels = <-100> <-100> <-100> <-100> <-100> <assistant3>
这种方法由于没有对中间轮次机器人回复的信息进行学习,因此存在着严重的信息丢失,是非常不可取的。
1.2 训练不高效方案
第二种方法是,把一个多轮对话拆解,构造成多条样本,以便对机器人的每轮回复都能学习。
inputs1 = <user1> <assistant1>
labels1 = <-100> <assistant1>
inputs2 = <user1> <assistant1> <user2> <assistant2>
labels2 = <-100> <-100> <-100> <assistant2>
inputs3 = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
labels3 = <-100> <-100> <-100> <-100> <-100> <assistant3>
这种方法充分地利用了所有机器人的回复信息,但是非常低效,模型会有大量的重复计算。
1.3 合适的数据组合方式
第三种方法是,直接构造包括多轮对话中所有机器人回复内容的标签,充分地利用了所有机器人的回复信息,同时也不存在拆重复计算,非常高效。目前大部分微调框架用的都是这个组合方式。
inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
labels = <-100> <assistant1> <-100> <assistant2> <-100> <assistant3>
我们为什么可以直接构造多轮对话样本?难道将第二轮和第三轮对话内容加入 inputs 中不会干扰模型对第一轮对话的学习吗?
答案是:不会。原因在于,作为一种语言模型,LLM(大语言模型)采用的是基于注意力机制的结构,其中的自注意力机制(Self-Attention)在处理输入时,具有天然的局部性约束。具体来说,LLM 在处理每一个输入时,使用掩码注意力(Masked Attention)来确保每个位置的预测只依赖于前面已经生成的内容,而不会提前'看到'后续的对话轮次。
也就是说,尽管输入数据中包含了多轮对话的信息,模型在进行每一轮对话的生成时,仅会关注当前回合的上下文,而不受后续轮次内容的影响。这样,第一轮的对话内容与后续轮次的对话并不会相互干扰,从而保持了学习的纯粹性。通过这种机制,模型能够有效地在多轮对话的框架下进行训练,同时保证每轮对话的独立性和准确性。
简而言之,LLM 能够通过其掩码机制在多轮对话中进行'局部'学习,每次生成的内容都仅与当前上下文相关,而不会受到其他轮次的干扰。
2. 各实验参数原理
2.1 LoRA 参数详解
LoRA(Low-Rank Adaptation)是一种针对大型语言模型的微调技术,旨在降低微调过程中的计算和内存需求。其核心思想是通过引入低秩矩阵来近似原始模型的全秩矩阵,从而减少参数数量和计算复杂度。
在 LoRA 中,原始模型的全秩矩阵被分解为低秩矩阵的乘积。具体来说,对于一个全秩矩阵 W,LoRA 将其分解为两个低秩矩阵 A 和 B 的乘积,即 W ≈ A * B。其中,A 和 B 的秩远小于 W 的秩,从而显著减少了参数数量。
上图为 LoRA 的实现原理,其实现流程为:
- 在原始预训练语言模型旁边增加一个旁路,做降维再升维的操作来模拟内在秩;
- 用随机高斯分布初始化 A,用零矩阵初始化 B,训练时固定预训练模型的参数,只训练矩阵 A 与矩阵 B;
- 训练完成后,将 B 矩阵与 A 矩阵相乘后合并预训练模型参数作为微调后的模型参数。
公式表示为:
$$W' = W + \Delta W = W + A \cdot B$$
其中,W 是原始的权重矩阵,A 是一个尺寸为 dr 的矩阵,B 是一个尺寸为 rd'的矩阵,r 是低秩矩阵的秩。通过这种分解,原始矩阵 W 的更新仅由 A 和 B 的乘积决定。进一步地,LoRA 引入了一个缩放因子 alpha,使得更新公式为:
$$W' = W + \frac{\alpha}{r} A \cdot B$$
那么在实际使用的时候,我们如何确定 lora 参数?这些参数的变化对实验结果产生什么影响?模型具体哪些部分参数需要使用 lora?等等这些问题,我们应该如何应对?下面我将详细介绍。
LoraConfig 各个参数设置
peft(Parameter-Efficient Fine-Tuning)库是一个用于高效微调大规模预训练模型的工具,旨在减少训练时的计算和存储成本,同时保持模型性能。它通过引入 LoRA、Adapter 等技术,使得只需调整部分参数即可实现有效的微调。LoraConfig 是 peft 库中的一个配置类,用于设置 LoRA 相关的超参数,如低秩矩阵的秩、缩放因子等,它帮助用户定制 LoRA 微调的细节,优化训练过程的效率和效果。
from peft import LoraConfig, TaskType
lora_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
target_modules=['up_proj', 'gate_proj', 'q_proj', 'o_proj', 'down_proj', 'v_proj', 'k_proj'],
task_type=TaskType.CAUSAL_LM,
inference_mode=False
)
target_modules
target_modules 是 LoRA(Low-Rank Adaptation)中的关键参数,用于指定模型中需要插入低秩矩阵调整的模块。LoRA 的核心思想是通过对预训练模型中的特定层进行低秩矩阵插入,实现参数高效微调而无需修改原始权重。对于语言模型,通常选择影响权重更新较大的模块,例如 q_proj 和 k_proj(负责查询和键的变换),v_proj(值的变换),以及 o_proj(输出投影)等。这些模块主要集中在自注意力和前馈网络中,通过插入的低秩矩阵调整这些模块的权重,使模型在保持原始能力的同时适应新任务,极大减少微调的计算和存储开销。
具体如下,我们使用 deepseek 观察模型每一层具体都是什么:
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(model_name)
print(model)
具体模型结构如下:
LlamaForCausalLM(
(model): LlamaModel(
(embed_tokens): Embedding(102400, 4096)
(layers): ModuleList(
(0-29): 30 x LlamaDecoderLayer(
(self_attn): LlamaSdpaAttention(
(q_proj): Linear(in_features=4096, out_features=4096, bias=False)
(k_proj): Linear(in_features=4096, out_features=4096, bias=False)
(v_proj): Linear(in_features=4096, out_features=4096, bias=False)
(o_proj): Linear(in_features=4096, out_features=4096, bias=False)
(rotary_emb): LlamaRotaryEmbedding()
)
(mlp): LlamaMLP(
(gate_proj): Linear(in_features=4096, out_features=11008, bias=False)
(up_proj): Linear(in_features=4096, out_features=11008, bias=False)
(down_proj): Linear(in_features=11008, out_features=4096, bias=False)
(act_fn): SiLU()
)
...
)
)
)
)
可以看到 deepseek 模型也是采取的 Llama 模型结构,那么具体哪些层会参与 lora 微调呢?下面将详细介绍。
-
Attention 层
- Self-attention 层: 这些层通常对模型性能影响较大。LoRA 会被应用于自注意力的查询(q_proj)、键(k_proj)、值(v_proj)和输出(o_proj)投影矩阵。这些矩阵包含了大量的可训练参数,因此是 LoRA 微调的理想目标。
- LlamaSdpaAttention 中的矩阵:
- q_proj: 查询投影
- k_proj: 键投影
- v_proj: 值投影
- o_proj: 输出投影
- Rotary Embedding: 虽然在一些实现中会对嵌入进行微调,但通常 LoRA 不会直接用于 rotary_emb,因为它通常是固定的。
-
MLP 层
- MLP 层中的 Gate、Up 和 Down 投影:
- gate_proj:控制门投影
- up_proj:上升投影
- down_proj:下降投影
- MLP 层的 Gate、Up 和 Down 投影通常涉及大量的可训练参数,因此对这些投影进行 LoRA 微调,可以在不显著增加计算负担的情况下优化模型表现。
-
LayerNorm 层
- RMSNorm: 在 Llama 中使用的是 LlamaRMSNorm(Root Mean Square Layer Normalization),它与标准的 LayerNorm 不同,但也可以通过 LoRA 微调。虽然这部分常常不会进行微调,但如果需要微调,通常会集中在注意力层和 MLP 层上。
-
Embedding 层
- embed_tokens:如果对词嵌入有需要进行微调,LoRA 也可以应用于嵌入矩阵。尤其在词汇量较大的情况下,嵌入矩阵的参数量非常庞大,这样进行 LoRA 微调也可以获得一定的性能提升。
-
线性层(lm_head)
- lm_head:在模型输出时,lm_head 是从隐藏层到词汇表的最后一层线性转换。通常,LoRA 不会直接应用于输出层,但在某些微调场景下,可以将 LoRA 应用于该层以调整模型输出。
总结: 一般来说,LoRA 微调会集中在以下层:Attention 层的查询、键、值和输出投影(q_proj, k_proj, v_proj, o_proj);MLP 层的 gate_proj、up_proj 和 down_proj;可能在某些场景下微调 embed_tokens 和 lm_head。
r、alpha、dropout
在模型微调的过程中,r、alpha 和 dropout 是常见的超参数,用于优化模型训练和提升其泛化能力。
- r:通常用于 LoRA(Low-Rank Adaptation)方法中,表示低秩矩阵的秩值。r 决定了微调时使用的低秩矩阵的维度,较小的 r 可以减少参数数量,从而提高训练效率,但可能牺牲一定的模型表现。较小的 r(例如 8-32)适用于较小模型或需要较低资源的情况,而较大的 r(例如 64-128)适用于更大规模的模型。
- alpha:是 LoRA 中的一个超参数,用来控制低秩矩阵的缩放因子。通过调整 alpha,可以平衡低秩矩阵的影响,使模型能够在微调过程中保持足够的表达能力。16-32 是比较常见的选择,较大的 alpha 值通常会增加模型的表达能力,但也可能增加训练难度。
- Dropout:是一种正则化技术,通过在训练过程中随机丢弃神经网络中的部分神经元来防止过拟合。dropout 率控制丢弃的概率,较高的 dropout 率有助于减少模型的复杂度,从而提升其在新数据上的泛化能力。对于大多数任务,0.2-0.3 是比较常见的取值,较低的 dropout 值(如 0.1)适合于较小的模型,而较高的 dropout 值(如 0.4-0.5)适合于较大的网络,尤其是在防止过拟合时。
总结:
- r:通常选择 8-128,根据任务和模型规模调整。
- alpha:常见值在 16-64,推荐 16-32。
- Dropout:常见值在 0.1-0.5,推荐 0.2-0.3。
task_type
在 LoraConfig 中的 task_type 是一个指定模型任务类型的参数,它帮助 LoRA 配置不同的微调策略,以适应特定的任务需求。task_type 可以有多个选项,通常包括以下几种类型:
- CAUSAL_LM:自回归语言建模任务,模型基于输入的部分文本(上下文)来预测下一个词,适用于生成任务,如文本生成和语言建模。
- SEQ_CLS:文本分类任务,模型将整个输入文本分类到某个类别。常见的应用包括情感分析、垃圾邮件检测、新闻分类等。
- SEQ_2_SEQ_LM:序列到序列的语言建模任务。该任务类型处理输入序列并生成一个输出序列。通常用于机器翻译、文本摘要等任务。
- TOKEN_CLS:标记分类任务,模型为输入文本的每个标记(通常是词或子词)分配一个类别标签。常见应用包括命名实体识别(NER)、词性标注(POS)、依存句法分析等。
- QUESTION_ANS:问答任务,模型根据输入的问题和上下文,提取答案。常见应用包括阅读理解、基于文档的问答等。
- FEATURE_EXTRACTION:特征提取任务,模型提取输入数据的隐藏状态(通常是编码器的输出),这些隐藏状态可以用于下游任务,如聚类、分类或作为其他任务的输入特征。
bias
在 LoraConfig 配置中,bias 参数用于指定 LoRA 微调时如何处理偏置(bias)项。具体来说,这个参数控制了在低秩适应中,是否保留或者修改偏置项。LoRA 微调一般会将权重矩阵拆分成低秩矩阵来减少训练时的计算开销,但偏置项通常会保留或处理得不同。
bias 参数的常见选项:
- "none":不对偏置项进行微调,也就是说,偏置项保持原样,不参与 LoRA 的低秩适应过程。这是默认选项,表示不修改偏置项,保持原有权重。
- "all":对所有的偏置项进行微调,这意味着 LoRA 不仅会对权重矩阵进行低秩适应,还会对偏置项进行相应的调整。
- "lora_only":仅对 LoRA 引入的低秩矩阵中的偏置项进行微调。即在 LoRA 的低秩变换部分,偏置项会被包含在内,并进行优化。
为什么选择 "none" 作为 bias 的值?在许多 LoRA 微调的实现中,偏置项通常被认为是模型的一个稳定部分,尤其是在进行低秩微调时,可能并不需要对它们进行调整。使用 "none" 的选择意味着微调过程只会集中在权重矩阵的低秩部分,而不涉及偏置项的变动,这有助于减少额外的计算和参数调节,保持模型的原始结构。
2.2 训练超参数设置
除了 LoRA 特有的参数外,常规的训练超参数同样至关重要,直接影响收敛速度和最终效果。
学习率(Learning Rate)
学习率是优化器在每一步更新权重时的步长。过大可能导致模型无法收敛甚至发散,过小则会导致训练时间过长或陷入局部最优。
- 建议范围:通常在 1e-5 到 5e-5 之间。对于 LoRA 微调,由于参数量较少,有时可以使用稍高的学习率,如 2e-4,但需谨慎验证。
- 调度策略:推荐使用 Cosine Decay 或 Linear Warmup + Cosine Decay 策略,先预热学习率再逐渐衰减,有助于稳定训练初期。
批次大小(Batch Size)
批次大小决定了每次梯度更新所使用的样本数量。较大的批次可以提供更稳定的梯度估计,允许更高的学习率,但受限于显存。
- 建议范围:根据显存情况调整,通常为 16、32 或 64。如果显存不足,可使用 Gradient Accumulation(梯度累积)来模拟更大的批次。
- 注意事项:过大的批次可能导致泛化能力下降,过小的批次可能导致训练不稳定。
优化器(Optimizer)
优化器负责根据梯度更新模型参数。常用的优化器包括 AdamW、AdamW8bit 等。
- AdamW:结合了 Adam 的自适应学习率和 Weight Decay(权重衰减),是目前微调大模型的主流选择。
- 参数设置:beta1 通常设为 0.9,beta2 设为 0.999,epsilon 设为 1e-8。Weight Decay 建议设为 0.01 左右以防止过拟合。
2.3 总结
完成上述参数配置后,即可开始微调训练。建议在训练过程中监控 Loss 曲线和验证集指标,若 Loss 震荡剧烈可适当降低学习率,若 Loss 下降缓慢可检查数据质量或调整 Batch Size。通过合理配置 LoRA 参数与训练超参数,能够显著提升 DeepSeek 等大模型在特定任务上的表现,同时保持高效的训练资源利用。