声明:本文部分内容使用AI辅助生成,经人工编辑、审核和补充个人经验。
更新说明:本文最后更新于 2026-05-11。
大模型微调实战踩坑记录
折腾大模型微调大半年,从7B到70B都试过,LoRA、QLoRA、全参数微调都踩过坑。记录一下数据准备、训练配置、显存优化的实战经验。
微调方法选择
方法对比踩坑
开始不知道选什么方法,全参数、LoRA、Prompt Tuning都试过。
| 方法 |
显存占用 |
训练速度 |
效果 |
适用场景 |
| 全参数微调 |
极高 |
慢 |
最好 |
数据充足、算力充沛 |
| LoRA |
低 |
快 |
好 |
大多数场景 |
| QLoRA |
极低 |
中等 |
较好 |
消费级显卡 |
| Prompt Tuning |
极低 |
最快 |
一般 |
简单任务 |
| Adapter |
低 |
快 |
较好 |
多任务场景 |
血泪教训:
- 7B模型全参数微调,单卡24GB不够,得用DeepSpeed ZeRO-3多卡
- 70B模型,QLoRA 4-bit量化,单卡48GB能跑,但效果比8-bit差一截
最终选择:
- 实验阶段:QLoRA快速验证
- 生产阶段:LoRA 8-bit或16-bit
- 追求极致:全参数+多卡
数据准备踩坑
数据格式
开始没搞清楚格式,直接用原始文本训练,效果极差。
错误示范:
1
| {"text": "问题:怎么安装Python? 答案:去官网下载安装包..."}
|
正确格式(Alpaca格式):
1 2 3 4 5
| { "instruction": "怎么安装Python?", "input": "", "output": "去官网下载安装包,双击运行..." }
|
ShareGPT格式(对话):
1 2 3 4 5 6 7
| { "messages": [ {"role": "system", "content": "你是Python专家"}, {"role": "user", "content": "怎么安装Python?"}, {"role": "assistant", "content": "去官网下载..."} ] }
|
坑:不同模型要求不同格式,必须对齐:
- LLaMA:Alpaca格式
- Qwen:ShareGPT格式
- ChatGLM:特殊格式
数据清洗
原始数据质量差,直接训练效果不行。
遇到的问题:
- 重复数据:爬取的问答数据大量重复,模型过拟合
- 长度过长:有的样本token数>8000,训练时OOM
- 格式不统一:有的用中文标点,有的用英文标点
- 答案质量差:有的答案明显错误
清洗流程:
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 34 35 36 37 38
| import json from datasets import Dataset
def clean_data(examples): """数据清洗函数""" cleaned = []
for example in examples: if example['instruction'] in seen_instructions: continue seen_instructions.add(example['instruction'])
text = example['instruction'] + example['output'] tokens = tokenizer.encode(text) if len(tokens) > 2048: continue
if len(example['output']) < 10: continue
if '我不知道' in example['output']: continue
example['instruction'] = example['instruction'].strip() example['output'] = example['output'].strip()
cleaned.append(example)
return cleaned
dataset = Dataset.from_json('raw_data.json') dataset = dataset.map(clean_data, batched=True) dataset.save_to_disk('cleaned_data')
|
清洗效果:
- 原始数据:100万条
- 去重后:40万条
- 长度过滤后:35万条
- 质量过滤后:28万条
数据增强
数据量不够,需要增强。
方法1:改写(Paraphrasing)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from transformers import pipeline
paraphraser = pipeline("text2text-generation", model="t5-base")
def augment_data(example): new_instruction = paraphraser( f"paraphrase: {example['instruction']}", max_length=128 )[0]['generated_text']
return { 'instruction': new_instruction, 'output': example['output'] }
|
方法2:回译(Back Translation)
1 2 3 4 5
| def back_translate(text, src_lang='zh', mid_lang='en'): en = translate_zh_to_en(text) zh = translate_en_to_zh(en) return zh
|
方法3:Self-Instruct
用GPT-4生成更多训练数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| seed_examples = load_seed_data()
for seed in seed_examples: prompt = f"""基于以下示例,生成5个相似的问答对:
示例: Q: {seed['instruction']} A: {seed['output']}
生成5个新问答对(格式:Q: ... A: ...):"""
new_examples = gpt4.generate(prompt)
|
LoRA配置踩坑
参数选择
LoRA参数不知道怎么设,试了一堆组合。
关键参数:
| 参数 |
说明 |
经验值 |
| r (rank) |
LoRA秩,越高表达能力越强 |
8-64,一般16 |
| lora_alpha |
缩放参数,通常=2r |
16-128 |
| lora_dropout |
防止过拟合 |
0.05-0.1 |
| target_modules |
哪些层加LoRA |
q_proj,v_proj |
测试数据(7B模型,中文问答任务):
| r |
alpha |
显存 |
训练时间 |
效果 |
| 4 |
8 |
14GB |
30min |
一般 |
| 8 |
16 |
15GB |
35min |
好 |
| 16 |
32 |
16GB |
40min |
很好 |
| 32 |
64 |
18GB |
50min |
极好 |
| 64 |
128 |
22GB |
70min |
极好(边际递减) |
结论:r=16是甜点,效果够用,显存友好。
target_modules选择
哪些层加LoRA影响效果。
常见选择:
1 2 3 4 5 6 7 8 9 10 11
| target_modules = ["q_proj", "v_proj"]
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj"]
target_modules = [ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj" ]
|
实测效果(7B模型):
| 配置 |
可训练参数 |
显存占用 |
效果 |
| q,v only |
4.2M |
15GB |
良好 |
| q,k,v,o |
16.8M |
16GB |
很好 |
| 所有线性层 |
50.3M |
18GB |
极好 |
推荐:q,k,v,o四件套,性价比最高。
训练配置踩坑
学习率
学习率设置是门玄学,调不好训练崩。
踩过的坑:
- lr=1e-3:loss爆炸,模型训崩
- lr=1e-5:loss不降,训了个寂寞
- lr=3e-4:LoRA标准值,效果还行
不同方法的学习率:
| 方法 |
推荐学习率 |
说明 |
| 全参数微调 |
1e-5 to 2e-5 |
要小,否则不稳定 |
| LoRA |
1e-4 to 3e-4 |
可以大一点 |
| QLoRA |
1e-4 to 2e-4 |
量化后梯度噪声大 |
| Prompt Tuning |
1e-2 to 1e-1 |
超大学习率 |
学习率调度:
1 2 3 4 5 6 7 8
| from transformers import TrainingArguments
training_args = TrainingArguments( learning_rate=2e-4, lr_scheduler_type="cosine", warmup_ratio=0.03, num_train_epochs=3, )
|
Warmup很重要:直接大学习率开始,loss会震荡。
Batch Size
显存和速度的权衡。
| Batch Size |
显存占用 |
训练时间/epoch |
效果 |
| 1 |
12GB |
2小时 |
一般 |
| 2 |
14GB |
1.2小时 |
好 |
| 4 |
18GB |
1小时 |
很好 |
| 8 |
OOM |
- |
- |
Gradient Accumulation:显存不够时的解决方案
1 2 3 4 5
| training_args = TrainingArguments( per_device_train_batch_size=2, gradient_accumulation_steps=2, )
|
注意:梯度累积等效于大batch,但训练更慢。
训练轮数
训多久是个问题。
Early Stopping:
1 2 3 4 5 6
| from transformers import EarlyStoppingCallback
trainer = Trainer( ..., callbacks=[EarlyStoppingCallback(early_stopping_patience=3)] )
|
经验:
- 7B模型:3-5个epoch
- 70B模型:1-2个epoch(数据要高质量)
Overfitting信号:
- 训练loss持续下降
- 验证loss上升
- 生成结果重复训练数据
显存优化
QLoRA配置
显存不够必须用QLoRA。
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
| from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", )
model = AutoModelForCausalLM.from_pretrained( model_name, quantization_config=bnb_config, device_map="auto", )
model = prepare_model_for_kbit_training(model)
lora_config = LoraConfig( r=16, lora_alpha=32, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", )
model = get_peft_model(model, lora_config)
|
显存对比:
| 配置 |
7B显存 |
13B显存 |
70B显存 |
| FP16 + LoRA |
18GB |
30GB |
140GB+ |
| 8-bit + LoRA |
10GB |
16GB |
80GB |
| 4-bit + LoRA |
6GB |
10GB |
48GB |
注意:4-bit量化有精度损失,对复杂任务效果下降明显。
Gradient Checkpointing
时间换空间,省50%显存。
1 2
| model.gradient_checkpointing_enable() model.enable_input_require_grads()
|
代价:训练速度慢30-40%。
DeepSpeed ZeRO
多卡训练的显存优化神器。
ZeRO-3配置:
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
| { "bf16": { "enabled": true }, "zero_optimization": { "stage": 3, "offload_optimizer": { "device": "cpu", "pin_memory": true }, "offload_param": { "device": "cpu", "pin_memory": true }, "overlap_comm": true, "contiguous_gradients": true, "sub_group_size": 1e9, "reduce_bucket_size": "auto", "stage3_prefetch_bucket_size": "auto", "stage3_param_persistence_threshold": "auto", "stage3_max_live_parameters": 1e9, "stage3_max_reuse_distance": 1e9, "stage3_gather_16bit_weights_on_model_save": true }, "gradient_accumulation_steps": 4, "gradient_clipping": 1.0, "train_batch_size": "auto", "train_micro_batch_size_per_gpu": "auto", "wall_clock_breakdown": false }
|
ZeRO-3效果:70B模型,8卡A100-80G能训全参数。
效果评估
自动评估指标
训练完要看效果。
1 2 3 4 5 6 7 8 9 10 11
| from evaluate import load
bleu = load("bleu") rouge = load("rouge")
texts = ["生成的文本1", "生成的文本2"] references = [["参考文本1"], ["参考文本2"]]
results = bleu.compute(predictions=texts, references=references)
|
指标局限性:
- BLEU:对短文本效果不好
- ROUGE:适合摘要任务
- Perplexity:越低越好,但和实际效果不一定相关
人工评估
最重要的评估方式。
评估维度:
| 维度 |
说明 |
评分 |
| 准确性 |
内容是否正确 |
1-5 |
| 相关性 |
是否回答用户问题 |
1-5 |
| 流畅性 |
语言是否通顺 |
1-5 |
| 安全性 |
是否有害内容 |
0/1 |
| 有用性 |
对用户是否有帮助 |
1-5 |
A/B测试:
1 2 3 4 5 6 7 8 9
| questions = load_test_questions()
for q in questions: answer_base = base_model.generate(q) answer_finetuned = finetuned_model.generate(q)
label = human_judge(q, answer_base, answer_finetuned)
|
模型合并与部署
LoRA权重合并
训练完的LoRA权重要和基座模型合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained( "meta-llama/Llama-3-8B", torch_dtype=torch.float16, device_map="auto" )
model = PeftModel.from_pretrained( base_model, "./lora_weights" )
model = model.merge_and_unload()
model.save_pretrained("./merged_model")
|
注意:合并后不能继续LoRA训练。
vLLM部署
生产环境用vLLM加速推理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from vllm import LLM, SamplingParams
llm = LLM( model="./merged_model", tensor_parallel_size=1, gpu_memory_utilization=0.9 )
sampling_params = SamplingParams( temperature=0.7, top_p=0.95, max_tokens=512 )
outputs = llm.generate(prompts, sampling_params)
|
吞吐量对比:
- Transformers:10 req/s
- vLLM:100+ req/s
常见问题
Loss不降
可能原因:
- 学习率太小
- 数据质量问题
- LoRA r太小
- 训练数据太少
解决:
- lr从1e-4开始
- 检查数据质量
- r从16开始
- 至少1万条高质量数据
Loss爆炸
可能原因:
- 学习率太大
- 数据有异常值
- 梯度爆炸
解决:
- 降低学习率
- 清洗数据
- 加gradient clipping
过拟合
症状:训练loss低,但测试效果差。
解决:
总结
大模型微调的核心经验:
- 数据为王:数据质量比数量重要,清洗是关键
- LoRA够用:大部分场景LoRA r=16就够
- 学习率关键:从标准值开始,根据loss调
- 显存优化:QLoRA、Gradient Checkpointing、DeepSpeed组合拳
- 效果评估:自动指标+人工评估结合
踩坑最多的地方:
- 数据格式不对,训了白训
- 学习率没调好,loss爆炸
- 过拟合严重,模型只会背答案
- 显存估算错误,训一半OOM