大模型微调:从 Step 到 Loss 到 LoRA,把训练拆明白
很多关于 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=2,gradient_accumulation_steps=4(effective_batch_size=8),跑 5 epochs:
steps_per_epoch = 3000 / 8 = 375
total_steps = 375 × 5 = 1875为什么框架不直接用 epoch?五个原因:
- 大规模预训练中 epoch 概念失效 —— 万亿 token 语料,训练通常不到一个 epoch
- 流式数据没有自然边界 —— HuggingFace 对流式数据把 epochs 设为天文数字,完全靠
max_steps停止 - Steps 跨配置可比 —— 1000 条样本的一个 epoch 和 100 万条的一个 epoch 计算量天差地别,step 把这个标准化了
- 调度粒度 —— "前 10% 做 warmup" 只有 step 粒度能表达,epoch 粒度只能选 0 或 1
- 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 配合的梯度极其简洁: SFT 和预训练的核心区别就在这里 —— 只对模型回答部分计算 loss,prompt 部分 mask 掉: PyTorch 的 为什么 mask prompt? 2025 年的新讨论:WIT 论文发现对 prompt token 施加小的非零权重(PLW 约 0.1)对短回复可能有益。但长回复时完全 mask(PLW=0)仍更好。Meta Llama 3/3.1 使用完全 prompt masking。 当数据集中不同类别样本数量不均衡时,可以通过加权让模型更关注少数但重要的类别。常见的做法: 一条权重为 2.0 的样本对梯度的贡献 = 2 条权重 1.0 的样本。这迫使模型在数据不均衡时优先学好稀缺类别。 上面是样本级的加权,近年研究开始探索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 而忽视真正承载语义的关键词。 使用 packing 时这个选择更关键 —— 一个 packed 序列包含多条不同长度的样本。 由于单 token loss 等于正确 token 概率的负对数,可以反推平均置信度: 平均概率 = exp(-loss),困惑度 = exp(loss) 注意:不要追求特定 loss 数字。低 loss 不等于好的下游表现。Cross-entropy 有自然下界(语言本身的随机性),永远不会到 0。 Loss 算完后,backward pass 计算梯度,然后优化器更新参数。这一环节有三组关键设计。 当 GPU 显存放不下目标 batch size 时,用梯度累积:每看 1 条样本算一次梯度,攒够 K 次后做一次参数更新(= 1 step)。 ① 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,纯为省显存时才用。 所有 LR scheduler 都以 step 为单位参数化: 在训练最初几十个 steps 中,学习率从 0(或接近 0)线性升到目标值。目的是在优化器动量统计量(AdamW 的一阶和二阶矩估计)尚未稳定时,避免大梯度更新。 LoRA 需要更少的 warmup,四个原因: Thinking Machines Lab 的 "LoRA Without Regret" 研究甚至发现完全不用 warmup也能取得竞争力的结果。 关键:改 batch size 会改 total_steps,进而改变整个 schedule 形状。每次调 batch 相关参数都要重算 warmup_steps。 LoRA 是微调中参数更新的"怎么改"决策,直接影响模型从 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 同时非零初始化可提高对次优学习率的鲁棒性。但标准零初始化仍是安全默认。 LoRA 的参数更新公式为: $$
W' = W + \frac{\alpha}{r} \times BA
$$ 其中 $\alpha$ 是缩放超参数,$r$ 是 LoRA 的 rank。$\alpha / r$ 这个比值直接控制 LoRA 更新的幅度: 2026 年 6 月的重要发现(arXiv 2606.12883):alpha 和学习率的作用不可互换 —— alpha 是有效优化的主驱动力,其增益无法通过调 LR 复制。LoRA 的频谱抑制效应平滑了优化景观,alpha 在不增加漂移比的情况下放大任务信号。 QLoRA 论文(Dettmers et al., NeurIPS 2023) 的关键发现:增加 adapter 覆盖的层数比增加 rank 更有效。 Transformer 中的 linear 层: Thinking Machines Lab (2025) 发现 MLP + 注意力 > 仅 MLP > 仅注意力。Amazon Science (2026) 发现 最佳实践: 前面四节描述了单个 step 内部发生的一切。但训练由成百上千个 steps 组成,需要宏观层面的监控和终止决策。 对齐规则: eval_loss 不等于实际效果 —— 这是最常踩的坑。eval_loss 衡量 token 级预测准确性,不是输出质量。一个 eval_loss 稍高的模型可能产生结构更好、事实更准确的输出。 推荐:定义 小技巧: Early stopping 通常减少 20-40% 训练时间,同时保持或提升泛化性能。 神经网络先学干净模式,再记忆噪声标签。Tian et al. (arXiv 2604.12469, 2026) 识别出三阶段:拟合 → 稳定 → 记忆化。 训练集中的标注错误在多 epoch 下被放大 —— 模型分不清"该学的规律"和"标错的标注"。这是"数据质量 > 参数调优"一直成立的根本原因。 缓解手段: 反直觉发现(Kopiczko et al., arXiv 2602.11149, 2026):在推理 SFT 任务上,400 条样本训练 128 epochs 超过了 51200 条样本训练 1 epoch。性能增益在模型完全记忆训练数据后趋于饱和。但这主要适用于 chain-of-thought 推理任务。prediction - truth,log 和 exp 互相抵消,产生干净稳定的梯度。3.2 第二层:哪些 Token 参与计算
tokens: [系统提示 ... 用户消息 ... ] [assistant 回答内容]
labels: [-100 -100 ... -100 ] [token_id ... EOS ]
不算 loss 算 lossnn.CrossEntropyLoss 默认 ignore_index=-100,该位置自动排除在 loss 和梯度之外。
3.3 第三层:样本间加权
# 按样本类别设置权重
category_weights = {
"reasoning": 2.0, # 推理类样本较少但重要
"coding": 1.5, # 代码类样本
"general": 1.0, # 通用对话类样本
}
sample_loss *= category_weights[sample_category]更细粒度的前沿方法(2025-2026)
3.4 第四层:Loss 归一化
方式
公式
特点
Per-token 平均(默认)
总 token loss / 非 mask token 数
长序列贡献更大,梯度更稳定
Per-sample 平均
每条样本 loss 独立归一化后取平均
每条样本权重相同,不受长度影响
两阶段(NVIDIA Nemotron)
Stage 1 per-token → Stage 2 per-sample
先充分学习,后均衡调优
Loss 值解读速查表
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
接近随机,通常是训练刚开始的状态
四、梯度与参数更新:从 Loss 到学习
4.1 梯度累积
batch_size=1 + accum=8 和 batch_size=8 不完全等价,两个差异源:4.2 学习率调度
get_scheduler(
name="cosine",
num_warmup_steps=56, # 0.03 × 1875 ≈ 56 steps
num_training_steps=1875 # 总 steps
)Warmup
方法
warmup_ratio
典型 LR
LoRA
0.03
1e-4 ~ 5e-4
全参数微调
0.05 ~ 0.10
1e-5 ~ 5e-5
常见 Schedule
current_step / total_steps 决定4.3 LoRA 适配器设计
初始化:为什么 Step 0 的 Loss 反映基础模型能力
alpha/rank 比值:缩放有效学习率
配置
缩放因子
效果
alpha = rank
1.0
保守稳定,安全默认
alpha = 2 × rank
2.0
有效 LR 翻倍,更激进
层选择:覆盖比深度更重要
q_proj, k_proj, v_proj, o_proj —— 控制信息路由和组合gate_proj, up_proj, down_proj —— 存储和检索事实知识o_proj 单独作为目标就能达到多层配置 98% 的准确率(但延迟降低 22.6%)。target_modules="all-linear"。Rank 选择
Rank
适用场景
说明
r = 8-16
简单任务(格式、风格)
QLoRA 发现 r > 8 后收益递减
r = 16-32
中等任务(指令遵循)
可靠起点
r = 32-64
复杂任务(领域知识、多任务)
监控过拟合
五、训练监控:跑多久、怎么停
5.1 Eval 策略
参数
典型值
作用
eval_steps150-500(或浮点数如 0.04)
每 N 步跑验证集
save_steps与 eval_steps 对齐
每 N 步保存 checkpoint
save_total_limit2-5
磁盘上最多保留几个 checkpoint
load_best_model_at_endTrue
训练结束后恢复到最佳 checkpoint
save_steps 必须是 eval_steps 的整数倍,否则最佳 checkpoint 可能没被保存。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
停后恢复最佳权重
5.3 多 Epoch 训练的退化风险
阶段
行为
Loss 表现
Epoch 1-2
学通用模式
快速下降
Epoch 3-4
精修边界
缓慢下降
Epoch 5+
开始记忆噪声
训练 loss 继续降,eval loss 上升
六、经验法则
决策点
建议
训练前
先算
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 覆盖大多数场景
参考文献