全参数微调
全参数微调(Full Fine-Tuning)是指更新模型所有参数的训练方式。它效果最好,但对显存要求最高,通常需要多卡集群。本篇我们用一个相对小的模型(Qwen2.5-0.5B)演示完整流程。
1. 全参数微调的特点
优点:
- 效果上限最高,模型可以完全适配新任务
- 实现简单,没有额外的参数结构
缺点:
- 显存占用极大:需要存储参数、梯度、优化器状态、激活值
- 容易过拟合:数据少时模型会记住训练样本
- 灾难性遗忘:可能丢失预训练阶段学到的通用能力
- 存储成本高:每次微调都得到一份完整模型副本
2. 显存占用分析
以 7B 模型为例,使用 Adam 优化器和 fp32 精度:
| 项目 | 显存占用 | 说明 |
|---|---|---|
| 模型参数 | 7B × 4 = 28GB | fp32 每个参数 4 字节 |
| 梯度 | 7B × 4 = 28GB | 每个参数一份梯度 |
| 优化器状态 | 7B × 8 = 56GB | Adam 维护一阶+二阶动量 |
| 激活值 | 视序列长度 | 反向传播需要 |
| 合计 | ~120GB+ |
这就是为什么 7B 全量微调要 80GB 显卡的 A100 才勉强能跑。
降低显存的常见手段:
- 混合精度(fp16/bf16):参数和梯度用 16bit,显存减半
- 梯度检查点(Gradient Checkpointing):用计算换显存
- 梯度累积(Gradient Accumulation):小 batch 多步累积,等效大 batch
- DeepSpeed ZeRO:将参数、梯度、优化器状态分散到多张卡
3. 准备数据
我们使用一个简单的中英翻译任务作为示例。先准备一份小数据集:
import json
samples = [
{"instruction": "将以下英文翻译成中文", "input": "Hello, world!", "output": "你好,世界!"},
{"instruction": "将以下英文翻译成中文", "input": "Good morning.", "output": "早上好。"},
{"instruction": "将以下英文翻译成中文", "input": "How are you?", "output": "你好吗?"},
# ... 实际项目至少几百到几千条
]
with open("translate_data.jsonl", "w", encoding="utf-8") as f:
for s in samples:
f.write(json.dumps(s, ensure_ascii=False) + "\n")
4. 完整训练脚本
新建 train_full.py:
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling,
)
# === 配置 ===
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
DATA_PATH = "translate_data.jsonl"
OUTPUT_DIR = "./output_full"
MAX_LENGTH = 512
# === 加载模型和分词器 ===
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype=torch.bfloat16, # 使用 bf16 节省显存
device_map="auto",
)
# === 数据预处理 ===
def preprocess(example):
user_content = example["instruction"]
if example.get("input"):
user_content += "\n" + example["input"]
messages = [
{"role": "user", "content": user_content},
{"role": "assistant", "content": example["output"]},
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False,
)
encoded = tokenizer(
text,
truncation=True,
max_length=MAX_LENGTH,
padding=False,
)
encoded["labels"] = encoded["input_ids"].copy()
return encoded
dataset = load_dataset("json", data_files=DATA_PATH, split="train")
dataset = dataset.map(preprocess, remove_columns=dataset.column_names)
dataset = dataset.train_test_split(test_size=0.1, seed=42)
# === 数据 collator(动态 padding)===
collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False,
)
# === 训练参数 ===
training_args = TrainingArguments(
output_dir=OUTPUT_DIR,
num_train_epochs=3,
per_device_train_batch_size=2,
per_device_eval_batch_size=2,
gradient_accumulation_steps=8, # 等效 batch_size = 2*8 = 16
learning_rate=2e-5, # 全量微调用小学习率
warmup_ratio=0.05,
lr_scheduler_type="cosine",
bf16=True, # 混合精度
gradient_checkpointing=True, # 梯度检查点
logging_steps=10,
eval_strategy="epoch",
save_strategy="epoch",
save_total_limit=2,
report_to="tensorboard",
push_to_hub=False,
)
# === 启动训练 ===
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
data_collator=collator,
tokenizer=tokenizer,
)
trainer.train()
trainer.save_model(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)
启动训练:
5. 关键超参数解读
| 参数 | 推荐值 | 说明 |
|---|---|---|
learning_rate |
1e-5 ~ 5e-5 | 全量微调用小学习率,避免破坏预训练权重 |
num_train_epochs |
1 ~ 3 | 通常 1-3 轮足够,太多会过拟合 |
per_device_train_batch_size |
1 ~ 4 | 看显存大小调整 |
gradient_accumulation_steps |
4 ~ 32 | 等效 batch_size = batch * accum |
warmup_ratio |
0.03 ~ 0.1 | 学习率预热比例 |
lr_scheduler_type |
cosine |
余弦衰减最稳定 |
weight_decay |
0 ~ 0.01 | 正则化,防过拟合 |
6. 训练监控
训练时关注三个指标:
Loss 曲线
正常情况:训练 loss 平滑下降,验证 loss 随后下降。
异常情况:
- 训练 loss 不降 → 学习率太小、数据有问题
- 训练 loss 下降但验证 loss 上升 → 过拟合,减少 epoch 或增加数据
- Loss 突然 NaN → 学习率太大、bf16 溢出
显存占用
如果显存不够:
- 降
per_device_train_batch_size - 升
gradient_accumulation_steps(保持等效 batch) - 开
gradient_checkpointing - 减小
MAX_LENGTH
TensorBoard 可视化
7. 多卡训练
如果有多张 GPU,使用 accelerate 启动分布式训练:
或使用 DeepSpeed:
创建 ds_config.json:
{
"bf16": {"enabled": true},
"zero_optimization": {
"stage": 2,
"overlap_comm": true,
"contiguous_gradients": true
},
"gradient_accumulation_steps": "auto",
"gradient_clipping": 1.0,
"steps_per_print": 10,
"train_batch_size": "auto",
"train_micro_batch_size_per_gpu": "auto"
}
在 TrainingArguments 中启用:
启动:
ZeRO 分级:
- Stage 1:分散优化器状态
- Stage 2:分散优化器状态 + 梯度
- Stage 3:全部分散,包括参数本身(适合超大模型)
8. 测试微调后的模型
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_DIR = "./output_full"
tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)
model = AutoModelForCausalLM.from_pretrained(
MODEL_DIR,
torch_dtype=torch.bfloat16,
device_map="auto",
)
messages = [
{"role": "user", "content": "将以下英文翻译成中文\nGood night, see you tomorrow."}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=128,
do_sample=False,
)
print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
9. 什么时候选择全量微调
满足以下条件可以考虑:
- 显存充足(A100 80GB 多卡)
- 数据量足够大(>10K 高质量样本)
- 任务和预训练分布差异较大(如英文模型适配中文医疗)
- 对效果要求极致
否则优先选 LoRA / QLoRA,下一篇详细介绍。
总结
- 全量微调更新所有参数,效果最好但显存需求大
- 7B 模型全量微调通常需要 80GB+ 显存
- 关键技术:bf16 混合精度、梯度检查点、梯度累积、DeepSpeed ZeRO
- 学习率要小(1e-5 ~ 5e-5),避免破坏预训练权重
- 监控训练/验证 loss 防止过拟合
- 实际项目中,多数场景用 LoRA/QLoRA 已经够用