跳转至

全参数微调

全参数微调(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)

启动训练:

python train_full.py

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 溢出

显存占用

watch -n 1 nvidia-smi

如果显存不够:

  • per_device_train_batch_size
  • gradient_accumulation_steps(保持等效 batch)
  • gradient_checkpointing
  • 减小 MAX_LENGTH

TensorBoard 可视化

tensorboard --logdir ./output_full/runs

7. 多卡训练

如果有多张 GPU,使用 accelerate 启动分布式训练:

accelerate config  # 第一次需要配置
accelerate launch train_full.py

或使用 DeepSpeed:

pip install 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 中启用:

training_args = TrainingArguments(
    ...,
    deepspeed="./ds_config.json",
)

启动:

deepspeed --num_gpus=4 train_full.py

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 已经够用

评论