Skip to content

微调预训练模型

从零训练 NanoGPT 让你吃透了原理;但真实工程里,我们几乎总是在已有预训练模型上微调,用少量数据「激活」它已有的语言能力。这一页给你两条最常用的实战路径。

1. 为什么要微调,而不是从头训

从头预训练: 需要海量语料 + 大量算力(动辄数周/数卡), 学的是"通用语言能力"
微调:      站在巨人肩膀上, 几千~几万条数据, 让模型适配你的领域/风格/任务
        通用预训练模型(GPT-2 / BERT / Qwen ...)
                 │  用你的小数据继续训练(改权重)

        你的领域模型(医疗问答 / 法律摘要 / 客服口吻 ...)

2. 路线 A:全参数微调一个 GPT-2(续写你的风格)

GPT架构 的预训练目标原封不动用在你的语料上——继续预测下一词,只是数据换成你的。

python
import torch
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForLanguageModeling

name = "gpt2"
tok = AutoTokenizer.from_pretrained(name)
tok.pad_token = tok.eos_token                      # GPT-2 没有 PAD, 用 EOS 顶替
model = AutoModelForCausalLM.from_pretrained(name)

# 你的语料(这里用一条演示, 实际换成公司文档/小说/对话记录)
ds = load_dataset("text", data_files={"train": "my_corpus.txt"})

def encode(b):
    return tok(b["text"], truncation=True, max_length=128)
ds = ds["train"].map(encode, batched=True).train_test_split(test_size=0.05)

# 关键: 语言建模 collator 会自动把 labels 设为 input(并把 padding 处设为 -100 忽略)
collator = DataCollatorForLanguageModeling(tokenizer=tok, mlm=False)

args = TrainingArguments(
    output_dir="gpt2-finetune",
    learning_rate=5e-5,            # 微调用比从零更小的学习率
    per_device_train_batch_size=8,
    num_train_epochs=3,
    eval_strategy="epoch",
    save_strategy="epoch",
    fp16=torch.cuda.is_available(),
    logging_steps=20,
)

trainer = Trainer(model=model, args=args,
                  train_dataset=ds["train"], eval_dataset=ds["test"],
                  data_collator=collator)
trainer.train()
trainer.save_model("gpt2-finetune/final")
  • mlm=False → 因果语言建模(GPT 式),labels=input_ids,padding 位被置 -100(交叉熵忽略)。
  • 训练完的 final/ 直接可用 AutoModelForCausalLM.from_pretrained("gpt2-finetune/final") 加载续写,风格已向你的语料靠拢。

3. 路线 B:LoRA 参数高效微调(大模型的主流做法)

全参数微调 7B 模型显存爆炸。LoRA 冻结原权重,只在旁边加一对极小的「低秩适配器」训练,参数量常不到 1%,效果却接近全量微调。

      原始权重 W (冻结, 不训练)
   x ───────────────► W·x

    └─► A·B (可训练, 秩 r 很小) ──► (A·B)·x
   输出 = W·x + (A·B)·x     只训练 A,B
python
from peft import LoraConfig, get_peft_model, TaskType
from transformers import AutoModelForCausalLM

base = AutoModelForCausalLM.from_pretrained("gpt2")   # 演示用小模型
lora = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=8, lora_alpha=16, lora_dropout=0.05,
    target_modules=["c_attn"],                         # 挂到注意力投影上
)
model = get_peft_model(base, lora)
model.print_trainable_parameters()   # trainable: xxx (≈ 总量的百分之零点几)
# 之后照常交给 Trainer 训练, 只更新 LoRA 权重
  • r(秩) 越小越省、越大表达越强,常见 4/8/16/64。
  • 训练产物只有几十 MB,可热插拔式加载到不同基座。
  • QLoRA = 4-bit 量化基座 + LoRA,让单张消费级显卡也能微调大模型。

4. Encoder 模型的微调(分类)

理解类任务的微调,就是 训练流程Trainer 里的例子:换 ForSequenceClassification 头、给标签、小学习率微调。此处不再重复。

5. 全量微调 vs LoRA vs 提示词:怎么选

能力够用? ──► 直接用 Prompt(零训练, 最省)      ← 先试这个!
   │不够

领域/风格适配? ──► LoRA(省显存, 多任务热切换)   ← 大模型默认
   │数据多、要深度改造

全参数微调                                  ← 数据充足且有算力
   │连语言都不是、要从零建模

从头预训练(NanoGPT 那种)                    ← 极少需要

工程第一原则:能靠 Prompt 解决的,别微调;能靠 LoRA 解决的,别全量。 微调是最后的、最贵的手段。

6. 微调避坑清单

  • 学习率要小(全量常 1e-5~5e-5;LoRA 可大些如 1e-4~2e-4),太大会「灾难性遗忘」。
  • 别把 padding 当词学:确保 labels 里 padding 位是 -100
  • 数据格式对齐预训练:分类/生成要用与基座一致的模板(对话模型尤其注意 ChatML 模板)。
  • 留验证集:监控是否过拟合到小数据、是否遗忘了通用能力。
  • 存 adapter 而非全模型(LoRA),便于版本管理与复用。

小结

  • 微调 = 用少量数据在预训练模型上「继续学」,远比从头训划算。
  • 全参数微调简单直接但吃资源;LoRA/QLoRA 是当今大模型微调主流。
  • 选型顺序:Prompt → LoRA → 全量 → 从头预训练,从便宜到昂贵。
  • 小心学习率过大导致的灾难性遗忘与 padding 污染。

恭喜!到这里你已经完成了从「理解原理」到「从零训练」再到「工程微调」的完整闭环。接下来看几篇进阶主题,把你的知识与现代大模型接轨 → 进阶主题·高效注意力