AI微调实战指南:从LoRA到全量训练

前言:为什么需要微调

直接使用基础模型确实能工作,但总有些不对味:它不懂你的行业黑话、记不住产品细节、回复风格也不统一。这些问题不是单纯靠 prompt 就能解决的。

微调的核心价值,就是让模型真正"学会"你的特定场景。但微调不是万能药,先问清楚三件事:

  • 数据量:少于 1000 条对子,效果提升可能有限
  • 算力:7B 模型至少要 24G 显存起步
  • 目标:风格调整用微调更划算,知识补充可能需要 RAG

一、微调方法全景

1.1 全量微调

全量微调更新模型的所有参数,效果最好但资源需求最高。

适用场景

  • 有充足算力(4×A100 80GB 或更多)
  • 需要深度改变模型行为
  • 训练数据充足(>10万条)

硬件需求

模型大小显存需求可用硬件
7B~80GBA100 (80GB)
13B~160GB2×A100 (80GB)
from transformers import AutoModelForCausalLM, TrainingArguments, Trainer

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b",
    torch_dtype=torch.bfloat16,
    device_map="auto"
)

training_args = TrainingArguments(
    output_dir="./full_finetune",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,
    learning_rate=2e-5,
    bf16=True,
    gradient_checkpointing=True,
    logging_steps=10,
    save_steps=500,
)

1.2 LoRA:参数高效微调

LoRA(Low-Rank Adaptation)的核心思想:不直接微调所有参数,而是通过低秩矩阵来调整。实际操作中,你要改的参数量能少 90% 以上。

原理: 假设原始权重是 $W$,训练时只更新 $\Delta W$。LoRA 把 $\Delta W$ 分解成两个小矩阵的乘积:$\Delta W = BA$。

其中 $A \in \mathbb{R}^{r \times d}$,$B \in \mathbb{R}^{d \times r}$,$r$ 是秩(通常取 8、16、32)。

硬件需求

模型大小显存需求可用硬件
7B~24GBRTX 3090/4090
13B~48GBA100 (40GB)
from peft import LoraConfig, get_peft_model

peft_config = LoraConfig(
    r=16,                  # 秩
    lora_alpha=32,         # 缩放因子,通常设为 2×r
    target_modules=["q_proj", "v_proj"],  # 只微调注意力层
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

rank 选择经验

  • 数据量少(<1000 条):r=4r=8
  • 数据量中等(1000-5000 条):r=8r=16
  • 数据量大(>5000 条):r=16r=32

1.3 QLoRA:量化低秩适配

当你发现 LoRA 还是跑不动时,就该考虑 QLoRA 了。它通过 4-bit 量化进一步压缩模型,显存需求能再降 60% 左右。

硬件需求

模型大小显存需求可用硬件
7B~12GBRTX 3060/4060
13B~20GBRTX 3090/4090
from transformers import BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",      # 推荐用 nf4
    bnb_4bit_compute_dtype=torch.float16,
    bnb_4bit_use_double_quant=True,  # 二次量化
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b",
    quantization_config=bnb_config,
    device_map="auto"
)

# 准备模型进行 k-bit 训练
from peft import prepare_model_for_kbit_training
model = prepare_model_for_kbit_training(model)

关键配置说明

  • bnb_4bit_quant_type="nf4":NF4 量化在保持精度上比 FP4 更好
  • bnb_4bit_use_double_quant=True:对量化参数再进行一次量化,能再省 ~0.4GB 显存
  • r=16:秩越大,表达能力越强,但参数量也越大

二、显存优化技术

2.1 梯度检查点

梯度检查点的原理:在反向传播时重新计算前向传播的中间激活值,而不是全部存下来。这用计算换空间,典型的时空权衡。

model.gradient_checkpointing_enable()  # 开启梯度检查点

效果:显存占用降约 24%,但训练速度慢 15-20%

2.2 混合精度训练

A100 支持 BF16,它比 FP16 的数值稳定性更好,而且不需要损失缩放。

training_args = TrainingArguments(
    # ...其他参数
    bf16=True,  # 改用 BF16
    fp16=False,
)

2.3 动态 Padding

from transformers import DataCollatorForLanguageModeling

data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False,
    pad_to_multiple_of=8  # 填充到 8 的倍数
)

# 预处理时不在这里做 padding
def preprocess_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        max_length=2048,
        padding=False,  # 关键:不在这里做 padding
    )

2.4 ZeRO 优化

DeepSpeed 的 ZeRO 优化把模型参数、梯度、优化器状态切片到不同 GPU 上。

{
  "zero_optimization": {
    "stage": 2,
    "offload_optimizer": {
      "device": "cpu",
      "pin_memory": true
    },
    "offload_param": {
      "device": "cpu"
    },
    "overlap_comm": true,
    "contiguous_gradients": true
  }
}

ZeRO 各阶段对比

  • Stage 1:只拆分优化器状态,省 4 倍显存
  • Stage 2:再拆分梯度,省 8 倍显存
  • Stage 3:连参数也拆分,省 N 倍显存(N 是进程数)

经验:先用 Stage 2,显存不够再加 offload。Stage 3 通信开销大,除非必要不建议用。

三、分布式训练

3.1 数据并行

数据并行是最基础的并行策略:把模型复制到每张卡上,每张卡吃不同批次的数据,算完梯度后同步。

import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP

def setup(rank, world_size):
    dist.init_process_group("nccl", rank=rank, world_size=world_size)

def train(rank, world_size):
    setup(rank, world_size)
    model = Model().to(rank)
    ddp_model = DDP(model, device_ids=[rank])

    for data, label in dataloader:
        optimizer.zero_grad()
        output = ddp_model(data)
        loss.backward()
        optimizer.step()

    dist.destroy_process_group()

数据并行的限制

  • 每张卡都要存完整模型副本
  • 如果单张卡连模型都放不下,需要模型并行

3.2 模型并行

层间并行:把模型的不同层放在不同的设备上。

class SplitModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.part0 = nn.Sequential(...).to('cuda:0')
        self.part1 = nn.Sequential(...).to('cuda:1')

    def forward(self, x):
        x = self.part0(x.to('cuda:0'))
        x = self.part1(x.to('cuda:1'))
        return x

张量并行:把层内的参数矩阵切分。

# 在两张卡上水平切分 W
W0 = W[:, :hidden_size//2]  # 在 GPU0
W1 = W[:, hidden_size//2:]  # 在 GPU1

# 每张卡计算部分结果
y0 = x @ W0
y1 = x @ W1

# 最后 all-reduce 合并结果
y = y0 + y1

3.3 三维并行

实际训练中,通常结合多种并行策略:

  • 数据并行:在设备组间进行
  • 张量并行:在设备组内进行
  • 流水线并行:按层分割到不同设备组

四、进阶:从 SFT 到 RLHF

4.1 指令微调(SFT)

指令微调只是教会了模型"什么是正确的",但没有教它"什么是更好的"。

数据质量大于数据量

def filter_duplicates(dataset, threshold=0.85):
    seen = set()
    filtered = []

    for item in dataset:
        content = item['input'] + item['output']
        content_hash = hashlib.md5(content.encode()).hexdigest()

        if content_hash not in seen:
            seen.add(content_hash)
            filtered.append(item)

    return filtered

训练参数经验(基于 LLaMA-7B):

  • 学习率:2e-5(太大会导致波动)
  • LoRA r=8, alpha=16(任务复杂可调高)
  • 有效 batch size:32(4 × 8 梯度累积)

4.2 奖励建模

奖励模型的核心是"偏好数据"——对同一个输入,给出两个不同的输出,让人标注哪个更好。

def compute_ranking_loss(reward_scores_A, reward_scores_B, margin=0.1):
    """
    计算排序损失
    如果 A 应该比 B 好,那么 reward_A 应该大于 reward_B + margin
    """
    return torch.clamp(margin + reward_scores_B - reward_scores_A, min=0).mean()

4.3 DPO:直接偏好优化

DPO 省略了显式的奖励模型,直接用偏好数据训练策略模型。

def compute_dpo_loss(policy_logits, ref_logits, chosen_mask, rejected_mask, beta=0.1):
    """DPO 损失计算"""
    log_ratio = policy_logits - ref_logits
    chosen_logps = log_ratio[chosen_mask]
    rejected_logps = log_ratio[rejected_mask]

    loss = -F.logsigmoid(beta * (chosen_logps - rejected_logps)).mean()
    return loss

DPO 的优势

  • 不需要单独训练奖励模型
  • 训练更稳定,不涉及复杂的 RL 超参数
  • 计算开销更小

4.4 什么时候需要 RLHF

不是所有场景都需要 RLHF。判断标准:

  1. 任务的主观性强:比如创意写作、对话生成
  2. 需要细粒度优化:不是对错问题,而是更好更坏的问题
  3. 有足够的标注资源

如果只是让模型完成明确的任务(如代码生成、信息抽取),指令微调往往就足够了。

五、常见问题与解决方案

5.1 显存不足

现象:CUDA out of memory

解决

  1. 减小 batch size,增加梯度累积
  2. 开启梯度检查点
  3. 使用 QLoRA 而不是 LoRA
  4. 启用 ZeRO offload

5.2 训练不收敛

现象:loss 突然变成 NaN 或剧烈震荡

解决

  1. 降低学习率到 1e-5 或 5e-5
  2. 检查数据质量,去掉异常样本
  3. 加上梯度裁剪 max_grad_norm=1.0
  4. 增加 warmup 步数

5.3 过拟合

现象:训练 loss 下降但验证 loss 反弹

解决

  1. 增加 dropout(LoRA 配置中 lora_dropout=0.1
  2. 减小 rank
  3. 数据增强
  4. 早停机制

5.4 推理速度慢

现象:训练完的模型推理比原模型慢

解决

  1. 合并 LoRA 权重:model.merge_and_unload()
  2. 使用 FlashAttention
  3. 模型量化
# 合并权重
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./merged_model")

六、效果评估

6.1 定量指标

指标基础模型微调后模型
Validation Loss1.890.72
代码规范符合率67%94%
领域准确率52%71%
幻觉率18%7%

6.2 定性评估

最重要的还是实际使用体验:

  1. 风格一致性:模型生成的输出是否符合预期风格
  2. 领域适配:能正确使用专业术语,不会胡编
  3. 通用能力保留:依然能解决复杂问题,没有完全遗忘
  4. 错误预判:对常见问题有意识,主动规避

七、方法选择建议

基于实践经验,给一个简单的选择建议:

  1. 小团队、资源有限:从 LoRA 开始,如果效果不够好,再考虑 QLoRA
  2. 有充足算力、追求最优效果:可以考虑全量微调
  3. 任务相对明确:指令微调 + 一些后处理规则可能就够了
  4. 任务高度主观、需要精细控制:RLHF/DPO 是值得投入的方向

技术选型要看场景和资源,而不是跟着论文走。

八、写在最后

微调不是魔法,它更多是在数据上做精细的工程。好的微调效果,往往来自于:

  1. 对任务场景的深刻理解
  2. 高质量、有针对性的数据准备
  3. 合理的模型选择和参数调优
  4. 持续的监控和迭代

路还在往前延伸,从 SFT 到 RLHF,再到现在的各种对齐技术。但回头想想,核心问题其实一直没变:如何让模型真正理解我们要它做什么,并且做得好。

剩下的就是在实践中慢慢摸索,找到适合自己场景的那条路。


本文整合了 18 篇 AI 微调实践文章,涵盖 LoRA、QLoRA、全量微调、分布式训练、内存优化、RLHF/DPO 等核心技术。

版权声明: 本文首发于 指尖魔法屋-AI微调实战指南:从LoRA到全量训练https://blog.thinkmoon.cn/post/ai-finetuning-comprehensive-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!