什么是指令微调(Instruction Tuning)

指令微调别名监督微调(Supervised Fine-tuning)、多任务提示训练(Multitask Prompted Training)。指使用自然语言形式的指令数据对预训练LLM进行参数微调,激活模型的指令遵循能力(Instruction Following),提升模型在Zero-Shot场景下解决未见任务的能力,sft会引入pretrain阶段未见过的special token学习新的语义,来增强模型的跨任务泛化能力和领域适配能力。sft首先要收集或构建指令化的实例,然后通过有监督的方式对LLM参数进行微调。接下来我们从指令构建和sft训练两方面展开。

指令数据的构建

指令格式化数据每一条都要是task-type类型的,通常包括任务描述(也称指令)、任务输入、任务输出和可选示例这几部分。指令数据有以下几个来源:

  • 现有NLP任务数据集:为输入输出对添加任务描述,比如“请翻译成英文”。但使用上多样性受限,与真实需求匹配度低。代表性的数据集有FLAN v2, P3, Super-Natural Instructions。
  • 日常对话数据:用用户真实查询作为指令,人工标注答案作为输出,高质量但标注成本高。代表性的有含多轮对话的ShareGPT, Dolly, OpenAssistant。
  • 合成数据:已有高质量指令作为上下文学习示例输入LLM,LLM半自动化生成指令-输出对。低成本,可扩展性高,但是需要过滤低质量数据。经典数据有Self-Instruct, WizardLM, Alpaca。

实际应用中大多是使用半自动化的合成数据,这里介绍2个经典的指令合成技术:

  • Self-Instruct:流程为人工初始化任务池 → 大模型生成新指令 → 过滤重复/低质(模型难以生成回复的指令、过长过短指令、输入输出重复指令)数据。最终用175条高质多样种子数据输入GPT3生成52K指令,即Alpaca数据集。
  • Evol-Instruct(WizardLM):基于初始指令数据集(如Alpaca)使用GPT3.5-Turbo进行深度演化(添加约束、深化推理步骤等提升指令复杂度)和广度演化(扩展指令主题覆盖范围),最后进行质量优化(移除ChatGPT认为差异小的指令;移除LLM难以响应的指令;移除仅包含标点符号和连词的指令或回复)。

那么自己如何构建高质量指令数据呢?sft数据的核心是数据多样性和质量,数量相对不重要。一个高质量指令数据集特点:指令多样性(指令类型、句式、表达风格)、回复准确安全、复杂性分层(包含不同难度样本,从简单问答到多步推理)。一些指令数据优化策略如下:

  • 指令格式设计:通常是“任务描述 + 输入-输出对 + 可选示例”的形式,结合Zero-Shot和Few-Shot数据的混合提示形式及引入CoT的效果更好。但是模型的生成速度和生成的token数量呈正相关,增加CoT或prompt过长都会增加模型耗时,这里需要做下权衡。
  • 指令数量与质量平衡:一定数量的高质量指令即可激活LLM的对话能力,指令过多模型性能提升会缓慢。指令清洗必不可少的就是去重,经典的指令去重方法包括MinHash、LSH局部敏感哈希或SimHash近似去重,这些方法对海量数据很高效,使用时可以直接调用py的datasketch库。除了清洗和去重,还会采用聚类分析来确保指令多样性,使用模型评分如使用GPT-4评估来对生成回复的质量自动化初筛,并最终结合人工评审来确保最高标准。
  • 多样性增强:prompt、answer的多样性很重要。可以使用主题重写或指令筛选增强多样性。

指令构建后的数据组织策略也很重要:

  • 平衡数据分布:混合多源数据,样本比例混合策略。同时设置最大容量,防大数据集主导采样。
  • 多阶段指令数据微调:在不同阶段逐渐增加指令难度和复杂性。
  • 预训练数据融合:OPT-IML加入5%预训练数据提升分类/生成任务。MiniCPM在预训练与sft间添加退火阶段,混合两类数据,结果优于先预训练再sft两阶段策略。

指令微调的训练策略

sft的很多技术和pretrain类似,比如优化器设置、高效训练技巧等,这里介绍一些不同点:首先是目标函数,pretrain建模的是词元级语言loss,sft是序列到序列的loss,sft中同质化严重的prompt不做loss计算,会加入loss mask机制避免重复计算前缀,仅对模型输出部分计算损失。训练参数上,pretrain训练数据量极大,batchsize也是千级别的,学习率相对较高(e⁻⁴~e⁻⁵),sft训练数据量在1-10w左右,最多不建议超过100w,epoch 1~3个,学习率极低(e⁻⁶~e⁻⁵),比如Alpaca的lr=2e⁻⁵ + 余弦衰减。另外pretrain不适用于多轮对话训练。

贴一下知乎大佬的经验总结。参考链接:

sft阶段做模型知识注入会影响模型的通用能力,非要知识注入应该continue pretrain。

炼丹技巧。小模型大学习率,大模型小学习率;起始训练适当做 warmup,几种主流的 lr_scheduler 都试一下,gradient_accumulation_steps 是个比较重要的参数,16 / 32 / 64 / 128 等数字都尝试下;按需开 dropout,不开没啥大事,开了反倒容易训炸。

模型欠拟合需要检查的:调整参数比如多训练1个epoch,调整学习率;如果任务较难看是否需要换更大参数的模型;检查训练数据是否准确;重写prompt增加背景知识,降低任务难度。过拟合不建议再调参数解决,最主要的解决方法是优化训练数据:删减对应task_type的数据,或扩充task_type数据多样性。阶梯形loss很多文章分析公认的结论是模型过拟合的体现。

灾难性遗忘:在原任务训练好的网络上再训练完新任务后,在原任务上表现崩溃式降低。

幻觉问题:广义幻觉指模型回答错误,无解,仅仅可以通过sft/rlhf让模型知道什么时候拒绝恢复,但没训练过的case依旧胡说八道。狭义幻觉指模型本身具备某个知识,但alignment后开始回答错误。可以通过魔改网络、loss、推理方式、训练方式等技巧缓解。

参数高效微调(PEFT)

全参微调需要较多的算力资源开销,参数高效微调(Parameter-efficient Fine-tuning)也叫轻量化微调旨在减少需要训练的模型参数量,同时保证微调后的模型性能和全参微调相当。下面介绍几种经典的PEFT方法。

低秩适配微调LoRA(Low-Rank Adaptation)

研究发现LLM在特定任务适配时参数通常是过参数化(Over-parameterized)的,即模型参数中大部分是冗余的。LoRA通过低秩分解将模型参数分解为两个低秩矩阵的乘积,实现参数的稀疏化。

具体来说,LoRA冻结原权重矩阵 $W_0$,添加低秩分解矩阵 $\Delta W = A \cdot B^T $($A,B \in \mathbb{R}^{H\times R}, R \ll H $)。前向计算:$h = W_0 x + A \cdot B^T x $ → 训练后合并 $W = W_0 + A B^T $。

全量微调显存为 $16P$($P$为参数量)。LoRA显存为$2P + 16P_{LoRA},P_{LoRA} \ll P$),对LLaMA(7B)进行LoRA微调,显存从108GB → 14GB。LoRA因其显存效率和无损合并特性,成为开源社区(LLaMA、BLOOM)的主流选择。

一些经典的LoRA变种:

  • AdaLoRA:动态调整不同矩阵的低秩参数 $R$,通过微调过程中的loss衡量不同参数矩阵对训练的重要性,重要性高的矩阵分配高秩。
  • QLoRA:原权重4比特量化 + LoRA权重16比特训练,保持微调效果同时节省显存($2P→0.5P$),65B模型可在单卡A6000(48GB)运行。
1
2
3
4
5
6
7
8
9
10
11
12
# LoRALinear层实现(PyTorch)
class LoRALinear(nn.Linear):
def __init__(self, in_features, out_features, r=8, dropout=0.05):
super().__init__(in_features, out_features)
self.A = nn.Linear(in_features, r, bias=False) # 低秩矩阵A
self.B = nn.Linear(r, out_features, bias=False) # 低秩矩阵B
self.dropout = nn.Dropout(dropout)
nn.init.normal_(self.A.weight, std=0.02)
nn.init.zeros_(self.B.weight)

def forward(self, x):
return F.linear(x, self.weight, self.bias) + self.B(self.A(self.dropout(x)))

其他PEFT方法

在预训练语言模型时广泛使用,但在LLM中的应用较少。

  • 适配器微调Adapter Tuning:在Transformer层间串行插入瓶颈网络(压缩→非线性→恢复维度),适用于资源受限场景。
  • 前缀微调Prefix Tuning:在注意力层的Key/Value前concat可训练前缀向量(虚拟词元),适用于生成任务。
  • 提示微调Prompt Tuning:在输入嵌入层添加连续提示向量(P-tuning用LSTM学习提示表示),适用于轻量化适配场景。

一些实战总结

现在有很多现成的支持sft和po的框架,其中Hugging Face是目前最主流的,可以使用Transformers+PEFT(+accelerate)库或者TRL库(专为sft和po设计),社区支持最好,但是需要编写代码并自行处理数据格式,以下给出了代码示例。还有一体化的高级框架如LLaMA-Factory,支持Web UI和命令行两种方式,初学者友好,抽象程度高,不适合深度定制。另外还有面向企业级和生产环境的DeepSpeed,由微软开发的深度学习库,并不是专门的SFT框架,但其ZeRO技术可以无缝集成到其他框架中。

实战中需要使用一些工具比如Weights & Biases (W&B)MLflowTensorBoard进行实验追踪。记录一些实验中的超参数(学习率、批次大小等)、训练和验证损失曲线、硬件利用率(GPU内存、使用率)、评估指标(例如,使用LLM-Evaluation-Harness的评分)、甚至模型预测样例。确保每次实验都可复现、可比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer

model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 设置PEFT配置
peft_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
task_type="CAUSAL_LM",
)

training_args = TrainingArguments(
output_dir="./results",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
fp16=True,
)

trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
peft_config=peft_config,
formatting_func=formatting_func, # 需要自己编写一个函数来格式化数据
)

trainer.train()

商业化广告场景应用

在ks商业化场景中常见的2种LLM范式分别是LLM生成式和LLM微调范式。

LLM生成式建模思路基本为根据下游任务设计prompt,进行tokenizer和embedding后输入到一个Transformer-based encoder-decoder或者decoder-only模型,最后生成任务的结果,比如OneRec架构最后生成量化后的item序列后再做Beam Search选出最终推荐结果。量化的好处是离散化输入原始的user和item文本为语义索引,可以大幅缩减prompt的长度,增加输入行为描述。这种生成式建模范式通常需要从头开始训练一个模型,比较花费心思。

最简单好上手的就是LLM微调范式,典型的一套预训练-SFT-PO的流程,当然预训练模型是直接使用现成的LLM,比如Qwen系列。通常也需要先将user及item一些行为、视频dense embedding RQVAE量化编码为语义id,以id+文本prompt的方式输入给LLM在下游任务sft,比如预测用户下一个转化的itemId token,item下一个转化的userId token。在原预测结果list基础上做向量匹配,落地上可以加一路召回。另一种比较简单是直接使用LLM做CoT推理,推理结果处理为BGE embedding或者量化id特征入模,先用大参数模型推理,再sft蒸馏小模型节省资源,可以落地到精排模型预估。

经典面试题

除了Early Stopping和减少参数量,从损失函数训练策略的角度可以采取哪些技术缓解sft过拟合?(ds答的挺好的/DOGE)

  1. 损失函数角度:
    • 标签平滑(Label Smoothing): 将原本one-hot的硬标签替换为“软标签”。例如,将正确类别的概率从1.0改为0.9,并将0.1的概率均匀分配给其他类别。这防止模型对训练标签过于自信,鼓励其学习更泛化的特征,在分类式SFT(如选择最佳回复)中特别有效。
    • 知识蒸馏(Knowledge Distillation): 使用一个更大、更强但可能更慢的teacher model来为训练数据生成软标签(概率分布),然后用这些软标签来训练student model。student model学习的是teacher model提炼出的、更平滑、更泛化的知识,而不仅仅是原始硬标签,从而减轻过拟合。
  2. 训练策略角度:
    • 加权采样(Weighted Sampling)课程学习(Curriculum Learning): 不是均匀地采样所有数据,而是优先学习更简单、更核心的样本,再逐步引入复杂和困难的样本。或者为不同质量的样本赋予不同权重(高质量样本权重更高)。这让模型先稳固基础,再学习细节,提高了学习效率并降低了拟合噪声的风险。
    • 对抗训练(Adversarial Training)最大化间隔(Max-Margin): 在损失函数中引入一项,旨在让正确回复的得分比错误回复的得分高出一个明显的margin。这鼓励模型学习更鲁棒的特征表示,而不仅仅是勉强拟合数据。
    • 参数高效微调(PEFT)技术: LoRA等技术本身也是一种极强的正则化。通过冻结原模型参数,只训练少量低秩矩阵,极大降低了可训练参数量,从而从根本上降低了过拟合的能力,同时还能保持甚至提升模型性能。