AI 18 分钟阅读

大模型微调:从 Step 到 Loss 到 LoRA,把训练拆明白

· 更新于 2026/6/29

很多关于 LLM 微调的文章把 steps/epochs、loss 设计、LoRA 配置、评估策略等拆成独立话题讲解。但实际上,这些全都是同一个训练循环中相互咬合的齿轮 —— 改动任何一个,都会连锁影响其他所有环节。

本文沿着一次训练实验的数据流,从数据加载到参数更新,完整拆解每一个设计决策。


一、训练循环:一个 Step 的全景

理解微调的第一步,是看清一个 training step 内部到底发生了什么

数据加载 → Forward Pass → Loss 计算 → Backward Pass → 参数更新
  (batch)    (模型预测)     (算 loss)    (算梯度)      (优化器 step)

这就是训练的原子操作。一个 step = 一次完整的"看数据 → 算 loss → 修正参数"循环。

Step 是训练引擎的时钟

所有训练机制都挂在 step 上 —— 学习率调度、日志记录、评估触发、checkpoint 保存、early stopping。Epoch(遍历一次完整数据集)只是 steps 的派生量:

steps_per_epoch = num_samples / effective_batch_size
total_steps = steps_per_epoch × num_epochs

以一个典型的小数据集微调为例:3000 条样本,batch_size=2gradient_accumulation_steps=4(effective_batch_size=8),跑 5 epochs:

steps_per_epoch = 3000 / 8 = 375
total_steps = 375 × 5 = 1875

为什么框架不直接用 epoch?五个原因:

  1. 大规模预训练中 epoch 概念失效 —— 万亿 token 语料,训练通常不到一个 epoch
  2. 流式数据没有自然边界 —— HuggingFace 对流式数据把 epochs 设为天文数字,完全靠 max_steps 停止
  3. Steps 跨配置可比 —— 1000 条样本的一个 epoch 和 100 万条的一个 epoch 计算量天差地别,step 把这个标准化了
  4. 调度粒度 —— "前 10% 做 warmup" 只有 step 粒度能表达,epoch 粒度只能选 0 或 1
  5. PyTorch 设计哲学 —— 原子操作就是 zero_grad → forward → backward → step

max_steps 设了正值会覆盖 num_epochs,Trainer 反算需要多少 epoch 循环但到 step 数就中断。num_train_epochs 支持浮点数(如 2.5 = 最后一个 epoch 跑 50%)。


二、数据流入:样本怎么进入模型

训练循环的起点是数据加载。三个关键决策:

2.1 Shuffling

每个 epoch 开始时数据重新打乱(HuggingFace Trainer 默认每 epoch 不同 seed)。

  • 不 shuffle 的后果:模型学到数据顺序中的假模式(如"正面情感之后总是负面情感"、"短文本后总跟长文本"),产生偏置学习
  • Shuffle 的真正价值:每个 epoch 的打乱顺序不同,同一条样本每次都和不同的样本组成 batch,模型在不同的上下文中反复学习同一条数据 —— 这才是多 epoch 训练的意义
  • 验证集永远不要 shuffle

2.2 Batch 构成与有效批大小

这是影响训练动态的最关键公式:

$$ \text{effective\_bs} = \text{per\_device\_bs} \times \text{grad\_accum\_steps} \times \text{num\_gpus} $$

为什么重要:改变有效批大小会连锁影响一切 ——

  • total_steps 变了 → LR schedule 形状变了
  • 梯度噪声水平变了 → 泛化特性变了
  • eval/save 频率(如果 step-based)变了

等效配置表(都达到 effective_batch_size = 1024):

per_device_bs accum_steps GPUs effective_bs
16 8 8 1024
16 16 4 1024
16 64 1 1024

增大 per_device_batch_size 更快但更耗显存;增大 gradient_accumulation_steps 省显存但更慢(顺序处理);增大 GPU 数量更快但需要更多硬件。

2.3 Padding vs Packing

短序列填充到最大长度(padding)会浪费大量算力 —— pad token 走完整 forward pass 但不贡献 loss。对长度差异大的数据集,浪费可达 ~50%。

Packing 把多条短序列拼进同一个 context window:

方式 加速比 风险
朴素 packing(无隔离) 2-5x 交叉污染:一条样本的 token 注意到另一条
Packing + position IDs + attention mask 2-5x 无风险,现代实现的标准做法
Padding-free(TRL 0.19+) ~2x 无风险,FFD 算法自动分组

Wang et al. (ACL Findings 2025) 发现:packing 的模型在各种 benchmark 上普遍优于 padding,且优势随模型规模增大。

2.4 VLM 的特殊性:图片 Token 的上下文占用

VLM 中图片经过 ViT 后变成大量视觉 token(LLaVA: 576, 早期融合模型: ~4096, Qwen2.5-VL 压缩后: ~144)。这些 token 不参与 loss 计算(label=-100),但占据大量 context。

一条典型 VLM SFT 样本的 token 分布:

[system ~200] [image ~800] [user ~100] [assistant ~150]
                                        只有这150个算loss

有效 loss token 可能只占总序列的 ~12%。这解释了 VLM 训练显存高但 loss 降得快 —— 每 step 真正学的信号不多。

ICLR 2025 缩放律研究的反直觉发现:推理最优策略是最大化 LLM 参数,最小化视觉 token 数 —— 有时压缩到每张图仅 1 个 token。


三、Loss 计算:模型怎么知道自己错了

数据进入模型后,forward pass 产生预测,接下来就是算 loss。这里有四层设计决策。

3.1 第一层:单 Token 的 Cross-Entropy

每个时间步 t,模型对整个词表输出一个概率分布,loss 就是正确 token 的负对数概率:

$$ L_t = -\log\, p(y_t \mid y_1, \dots, y_{t-1}) $$

概率来自 softmax 归一化:

$$ p(y_t \mid y_{

其中 $h_t$ 是第 t 步的隐状态向量,$e_w$ 是词表中 token w 对应的输出嵌入向量,$V$ 是完整词表。

整个序列的 loss 就是所有参与计算的 token 的平均:

$$ L = \frac{1}{N} \sum_{t=1}^{N} L_t $$

为什么 cross-entropy 好用? 它和 softmax 配合的梯度极其简洁:prediction - truth,log 和 exp 互相抵消,产生干净稳定的梯度。

3.2 第二层:哪些 Token 参与计算

SFT 和预训练的核心区别就在这里 —— 只对模型回答部分计算 loss,prompt 部分 mask 掉

tokens:  [系统提示 ... 用户消息 ... ] [assistant 回答内容]
labels:  [-100  -100  ... -100       ] [token_id ... EOS  ]
              不算 loss                     算 loss

PyTorch 的 nn.CrossEntropyLoss 默认 ignore_index=-100,该位置自动排除在 loss 和梯度之外。

为什么 mask prompt?

  • 推理时模型只需生成回答,训练它"预测" prompt 浪费梯度更新
  • 不同样本的 system prompt 各不相同,训练预测它们会产生矛盾梯度信号
  • 模型学习容量有限,应集中在回答质量上

2025 年的新讨论:WIT 论文发现对 prompt token 施加小的非零权重(PLW 约 0.1)对短回复可能有益。但长回复时完全 mask(PLW=0)仍更好。Meta Llama 3/3.1 使用完全 prompt masking。

3.3 第三层:样本间加权

当数据集中不同类别样本数量不均衡时,可以通过加权让模型更关注少数但重要的类别。常见的做法:

# 按样本类别设置权重
category_weights = {
    "reasoning": 2.0,   # 推理类样本较少但重要
    "coding": 1.5,      # 代码类样本
    "general": 1.0,     # 通用对话类样本
}
sample_loss *= category_weights[sample_category]

一条权重为 2.0 的样本对梯度的贡献 = 2 条权重 1.0 的样本。这迫使模型在数据不均衡时优先学好稀缺类别。

更细粒度的前沿方法(2025-2026)

上面是样本级的加权,近年研究开始探索token 级的加权 —— 不是所有 token 对学习同等重要:

  • EAFT(Entropy-Adaptive Fine-Tuning) arXiv 2601.02151, 2025 提出根据模型对每个 token 的预测熵来动态调整权重。高熵 token(模型不确定、需要学习的位置)获得更高权重,低熵 token(模型已经能正确预测的位置)权重降低。核心思路是把监督信号集中在模型真正薄弱的地方,避免在已掌握的 token 上浪费梯度。实验发现 mask 掉底部 15% 的"自信但预测错误"的 token 能有效减少通用能力退化。

  • DFT(Dynamic Fine-Tuning) 将每个 token 的 loss 乘以模型对该 token 的预测概率作为缩放因子。模型已经能高概率预测正确的 token 自动获得更低的 loss 权重,而模型预测概率低的 token(更需要学习的位置)保持原始权重。相比 EAFT 使用熵,DFT 直接用概率值做缩放,实现更简单。

  • SFT-GO(SFT with Group Optimization) arXiv 2602.01227, 2025 借鉴 DRO(Distributionally Robust Optimization)思想,先用 TF-IDF 等统计指标将回答中的 token 按信息量分组(如高信息量词 vs 停用词),然后优化各组中表现最差的那组的 loss。这避免了模型在大量低信息量 token(如"the"、"is")上刷低 loss 而忽视真正承载语义的关键词。

3.4 第四层:Loss 归一化

方式 公式 特点
Per-token 平均(默认) 总 token loss / 非 mask token 数 长序列贡献更大,梯度更稳定
Per-sample 平均 每条样本 loss 独立归一化后取平均 每条样本权重相同,不受长度影响
两阶段(NVIDIA Nemotron) Stage 1 per-token → Stage 2 per-sample 先充分学习,后均衡调优

使用 packing 时这个选择更关键 —— 一个 packed 序列包含多条不同长度的样本。

Loss 值解读速查表

由于单 token loss 等于正确 token 概率的负对数,可以反推平均置信度:

Loss 值 平均概率 困惑度 含义
0.1 ~90% ~1.1 非常自信
0.5 ~61% ~1.65 SFT 正常收敛范围
1.0 ~37% ~2.7 训练早期,模型仍在学习
2.0 ~14% ~7.4 低置信,检查数据或学习率
5.0+ < 1% > 100 接近随机,通常是训练刚开始的状态

平均概率 = exp(-loss),困惑度 = exp(loss)

注意:不要追求特定 loss 数字。低 loss 不等于好的下游表现。Cross-entropy 有自然下界(语言本身的随机性),永远不会到 0。


四、梯度与参数更新:从 Loss 到学习

Loss 算完后,backward pass 计算梯度,然后优化器更新参数。这一环节有三组关键设计。

4.1 梯度累积

当 GPU 显存放不下目标 batch size 时,用梯度累积:每看 1 条样本算一次梯度,攒够 K 次后做一次参数更新(= 1 step)。

batch_size=1 + accum=8batch_size=8 不完全等价,两个差异源:

① Loss 归一化 Bug(影响最大)

跨不同长度 mini-batch 累积时,简单平均会产生错误的总 loss —— 可能比真实 full-batch loss 大 G 倍(G 为累积步数)。正确做法是所有累积步骤的 token loss 总和除以所有非 padding token 总数。

这个 bug 由 Unsloth 在 2024 年发现并修复,随后被 HuggingFace Transformers 采纳确保你的框架版本包含修复。

② 浮点精度

BFloat16 只有约 2.4 位十进制精度,累加多个小梯度的数值结果与一次大 batch 计算不同。效果一般可忽略,但在极端配置下(如 accum=64)可能有可测量的偏差。

建议:能放下就不用 accum,纯为省显存时才用。

4.2 学习率调度

所有 LR scheduler 都以 step 为单位参数化:

get_scheduler(
    name="cosine",
    num_warmup_steps=56,     # 0.03 × 1875 ≈ 56 steps
    num_training_steps=1875  # 总 steps
)

Warmup

在训练最初几十个 steps 中,学习率从 0(或接近 0)线性升到目标值。目的是在优化器动量统计量(AdamW 的一阶和二阶矩估计)尚未稳定时,避免大梯度更新。

LoRA 需要更少的 warmup,四个原因:

  1. 可训练参数少(~1-2%),优化景观更平滑
  2. 冻结的基础权重防止灾难性遗忘
  3. 低秩约束本身就是隐式正则化
  4. 更高的学习率容忍度
方法 warmup_ratio 典型 LR
LoRA 0.03 1e-4 ~ 5e-4
全参数微调 0.05 ~ 0.10 1e-5 ~ 5e-5

Thinking Machines Lab 的 "LoRA Without Regret" 研究甚至发现完全不用 warmup也能取得竞争力的结果。

常见 Schedule

  • Cosine decay(主流):慢起步衰减 → 中期加速 → 末期趋缓。形状由 current_step / total_steps 决定
  • Linear decay:线性下降到 0
  • WSD(Warmup-Stable-Decay):维持峰值 LR 的"稳定期"后快速衰减,适合不确定总步数的场景
  • Constant:LoRA 短训练中也能用

关键:改 batch size 会改 total_steps,进而改变整个 schedule 形状。每次调 batch 相关参数都要重算 warmup_steps。

4.3 LoRA 适配器设计

LoRA 是微调中参数更新的"怎么改"决策,直接影响模型从 loss 中学到什么、学多少。

初始化:为什么 Step 0 的 Loss 反映基础模型能力

LoRA 的权重增量为 $\Delta W = BA$,其中 A 用 Kaiming 初始化,B 初始化为零,所以训练开始时 $\Delta W = 0$,模型行为与预训练模型完全一致。

因此 step 0 的 loss 反映的是 base model 对当前任务格式的先验水平。如果初始 loss 很高(如 > 5),说明预训练模型对你的数据格式几乎没有先验 —— 这在自定义输出格式时很常见。

ICML 2025 新发现"Beyond Zero Initialization" 发现 A 和 B 同时非零初始化可提高对次优学习率的鲁棒性。但标准零初始化仍是安全默认。

alpha/rank 比值:缩放有效学习率

LoRA 的参数更新公式为:

$$ W' = W + \frac{\alpha}{r} \times BA $$

其中 $\alpha$ 是缩放超参数,$r$ 是 LoRA 的 rank。$\alpha / r$ 这个比值直接控制 LoRA 更新的幅度:

配置 缩放因子 效果
alpha = rank 1.0 保守稳定,安全默认
alpha = 2 × rank 2.0 有效 LR 翻倍,更激进

2026 年 6 月的重要发现arXiv 2606.12883):alpha 和学习率的作用不可互换 —— alpha 是有效优化的主驱动力,其增益无法通过调 LR 复制。LoRA 的频谱抑制效应平滑了优化景观,alpha 在不增加漂移比的情况下放大任务信号。

层选择:覆盖比深度更重要

QLoRA 论文(Dettmers et al., NeurIPS 2023) 的关键发现:增加 adapter 覆盖的层数比增加 rank 更有效。

Transformer 中的 linear 层:

  • 注意力层q_proj, k_proj, v_proj, o_proj —— 控制信息路由和组合
  • MLP/FFN 层gate_proj, up_proj, down_proj —— 存储和检索事实知识

Thinking Machines Lab (2025) 发现 MLP + 注意力 > 仅 MLP > 仅注意力。Amazon Science (2026) 发现 o_proj 单独作为目标就能达到多层配置 98% 的准确率(但延迟降低 22.6%)。

最佳实践target_modules="all-linear"

Rank 选择

Rank 适用场景 说明
r = 8-16 简单任务(格式、风格) QLoRA 发现 r > 8 后收益递减
r = 16-32 中等任务(指令遵循) 可靠起点
r = 32-64 复杂任务(领域知识、多任务) 监控过拟合

五、训练监控:跑多久、怎么停

前面四节描述了单个 step 内部发生的一切。但训练由成百上千个 steps 组成,需要宏观层面的监控和终止决策。

5.1 Eval 策略

参数 典型值 作用
eval_steps 150-500(或浮点数如 0.04) 每 N 步跑验证集
save_steps 与 eval_steps 对齐 每 N 步保存 checkpoint
save_total_limit 2-5 磁盘上最多保留几个 checkpoint
load_best_model_at_end True 训练结束后恢复到最佳 checkpoint

对齐规则save_steps 必须是 eval_steps 的整数倍,否则最佳 checkpoint 可能没被保存。

eval_loss 不等于实际效果 —— 这是最常踩的坑。eval_loss 衡量 token 级预测准确性,不是输出质量。一个 eval_loss 稍高的模型可能产生结构更好、事实更准确的输出。

推荐:定义 compute_metrics 返回任务特定指标(准确率、F1、BLEU 等),设 metric_for_best_model="your_metric"

小技巧eval_steps 设为浮点数(如 0.04)时被解释为总 steps 的比例,可以自适应不同数据集大小。

5.2 Early Stopping

参数 推荐值 说明
patience 2-3 连续 N 次 eval 无改善就停
min_delta 0.001-0.01 最小改善阈值,防噪声触发
restore_best True 停后恢复最佳权重

Early stopping 通常减少 20-40% 训练时间,同时保持或提升泛化性能。

5.3 多 Epoch 训练的退化风险

神经网络先学干净模式,再记忆噪声标签。Tian et al. (arXiv 2604.12469, 2026) 识别出三阶段:拟合 → 稳定 → 记忆化

阶段 行为 Loss 表现
Epoch 1-2 学通用模式 快速下降
Epoch 3-4 精修边界 缓慢下降
Epoch 5+ 开始记忆噪声 训练 loss 继续降,eval loss 上升

训练集中的标注错误在多 epoch 下被放大 —— 模型分不清"该学的规律"和"标错的标注"。这是"数据质量 > 参数调优"一直成立的根本原因。

缓解手段

  1. Early stopping(主要防线)
  2. LoRA(低秩约束限制记忆容量)
  3. Dropout(配合高 LR + 多 epoch)
  4. 数据清洗(从源头减少噪声)

反直觉发现Kopiczko et al., arXiv 2602.11149, 2026):在推理 SFT 任务上,400 条样本训练 128 epochs 超过了 51200 条样本训练 1 epoch。性能增益在模型完全记忆训练数据后趋于饱和。但这主要适用于 chain-of-thought 推理任务。


六、经验法则

决策点 建议
训练前 先算 total_steps,它决定 LR schedule、warmup 时长、eval 频率
Batch size 改了就重算一切 warmup_steps、eval_steps
学习率 LoRA 用 1e-4,全参用 1e-5;LoRA warmup 0.03
LoRA 层 target_modules="all-linear" 比提高 rank 更有效
LoRA rank 大多数任务 r=16-32 足够;r > 8 后收益递减
alpha 2 × rank 开始,发散则降到 = rank
Packing 能用就用,确保有 attention mask 隔离
Eval 用任务特定指标,不只盯 eval_loss
停止 永远开 early stopping,patience=2-3
多 epoch 3-5 epoch + early stop 覆盖大多数场景

参考文献