大模型微调实战踩坑记录

声明:本文部分内容使用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:特殊格式

数据清洗

原始数据质量差,直接训练效果不行。

遇到的问题

  1. 重复数据:爬取的问答数据大量重复,模型过拟合
  2. 长度过长:有的样本token数>8000,训练时OOM
  3. 格式不统一:有的用中文标点,有的用英文标点
  4. 答案质量差:有的答案明显错误

清洗流程

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:
# 1. 去重(简单版本)
if example['instruction'] in seen_instructions:
continue
seen_instructions.add(example['instruction'])

# 2. 长度过滤
text = example['instruction'] + example['output']
tokens = tokenizer.encode(text)
if len(tokens) > 2048: # 超长丢弃
continue

# 3. 质量过滤
if len(example['output']) < 10: # 答案太短
continue

if '我不知道' in example['output']: # 无效答案
continue

# 4. 格式统一
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):
# 改写instruction
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, # 预热3%
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, # 小batch
gradient_accumulation_steps=2, # 累积2步
# 实际batch_size = 2 * 2 = 4
)

注意:梯度累积等效于大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

# 4-bit量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # 嵌套量化
bnb_4bit_quant_type="nf4", # NF4比FP4效果好
)

# 加载量化模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto", # 自动分配层到GPU/CPU
)

# 准备模型
model = prepare_model_for_kbit_training(model)

# LoRA配置
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"
)

# 加载LoRA权重
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不降

可能原因

  1. 学习率太小
  2. 数据质量问题
  3. LoRA r太小
  4. 训练数据太少

解决

  • lr从1e-4开始
  • 检查数据质量
  • r从16开始
  • 至少1万条高质量数据

Loss爆炸

可能原因

  1. 学习率太大
  2. 数据有异常值
  3. 梯度爆炸

解决

  • 降低学习率
  • 清洗数据
  • 加gradient clipping

过拟合

症状:训练loss低,但测试效果差。

解决

  • 加dropout
  • 早停
  • 减少epoch
  • 数据增强

总结

大模型微调的核心经验:

  1. 数据为王:数据质量比数量重要,清洗是关键
  2. LoRA够用:大部分场景LoRA r=16就够
  3. 学习率关键:从标准值开始,根据loss调
  4. 显存优化:QLoRA、Gradient Checkpointing、DeepSpeed组合拳
  5. 效果评估:自动指标+人工评估结合

踩坑最多的地方:

  • 数据格式不对,训了白训
  • 学习率没调好,loss爆炸
  • 过拟合严重,模型只会背答案
  • 显存估算错误,训一半OOM