1.6 大模型监督微调(SFT)的参数设置和训练技巧
SFT(监督微调)训练实战指南:从参数到技巧,一文讲透
前面的小节详细介绍了大模型SFT的数据部分,包括合成、过滤、多样性等等,数据极大的影响着大模型SFT的效果。介绍完数据从这一节开始具体介绍大模型SFT的过程,包括参数设置、训练策略、训练技巧、效果评估等等。
1、SFT训练参数设置
- SFT 很少魔改 Loss/策略,训练策略基本和大模型预训练时保持一致,重点在 数据 与 训练可控性。
- 每次开跑前,把这些参数逐一过一遍:
epoch/gradient_accumulation_steps/global_batch_size/learning_rate/lr_scheduler_type/dropout/zero_stage/max_seq_len/offload/gradient_checkpointing/seq_parallel_size/weight_decay/per_device_train_batch_size/num_warmup_steps。 - 一般 1 epoch 就够;对于垂域+小数据(≤1 万条) 可设 3 epochs 以获得更高拟合。
- SFT 学习率 ≈ Pretrain 的 10倍:若预训练 LR=1.5×10−6\text{LR}=1.5\times 10^{-6}LR=1.5×10−6,SFT 可取 ≈3×10−5\approx 3\times 10^{-5}≈3×10−5。
- Deepspeed:显存足够首选 ZeRO-2;ZeRO-3 在弱带宽集群上 训练会显著变慢。 (ZeRO-2 和 ZeRO-3 的核心区别在于状态拆分的粒度和通信开销,ZeRO-2相当于大家自己留着全套模型,只分工存放优化器和梯度的一部分。而ZeRO-3是大家连模型本体都分开存,想用哪块就去别人那里“借”,所以通信更频繁)
- Packing:SFT 阶段不建议默认开启。对短 query/短答案拟合有损,尤其是数据量小或难样本占比高时。
-(在正常训练中,每条样本(query + answer)会被单独处理,并且都被 pad 到同一个 max_seq_length。如果样本普遍很短,就会产生大量 padding token → 浪费显存和算力。
Packing 会把多条样本拼接到同一个序列里(中间用特殊分隔符 < eos > / < sep >),尽量填满 max_seq_length,减少 padding)
1. 关键参数与可解释性
1.1 epoch
- 经验值:通用 SFT 设 1;垂域小数据(≤104\leq 10^{4}≤104 样本)可设 3 提升记忆度与可用性。
- 风险:过多 epoch → 过拟合、风格僵化、幻觉式“背书”增强。
1.2 梯度累计gradient_accumulation_steps(GAS) 与 有效批量Global Batch Size(GBS)
gradient_accumulation_steps(简称 GAS) 的作用,就是用多次小批量的前向+反向计算,来累积梯度,相当于变相增大批量大小,直到累计到指定步数后才更新一次模型参数。
在常规训练中,每个小批量(mini-batch)都会立即做一次前向计算、反向传播,并更新参数。但如果显存不足,没法一次放下一个很大的批量,就可以:
用较小的 per_device_batch_size(每张卡上的批量大小)跑一次前向+反向计算。不立刻更新参数,而是把计算出的梯度保存下来。
重复上面的过程多次(次数由 gradient_accumulation_steps 决定),不断把梯度加起来。到了设定的累计步数后,再一次性更新参数,然后梯度清零,开始下一轮累计。
那么一次参数更新的**有效批量(Global Batch Size)**就是:
GBS (Global Batch Size) = GAS×per_device_batch× devices \text{GBS} \;(\text{Global Batch Size}) \;=\; \text{GAS} \times \text{per\_device\_batch} \times \text{\ devices} GBS(Global Batch Size)=GAS×per_device_batch× devices
其中 GAS=gradient_accumulation_steps\text{GAS}=\text{gradient\_accumulation\_steps}GAS=gradient_accumulation_steps。
接下来计算一下每个 epoch 需要多少次参数更新(steps)
- 每个 epoch 的 优化步数:
steps_per_epoch = ⌈NGBS⌉ \text{steps\_per\_epoch} \;=\; \left\lceil \frac{N}{\text{GBS}} \right\rceil steps_per_epoch=⌈GBSN⌉
NNN 为样本条数(或样本对数)。你有 N 条训练样本,每次更新参数时会用到的有效批量大小是 Global Batch Size(GBS),也就是所有设备加上梯度累计之后的总批量。那么一个 epoch 需要的更新次数,就是 “总样本数除以 GBS”,如果除不尽,就向上取整(因为最后一次可能不满一整个批量)。GBS 越大,每个 epoch 的更新次数就越少;GBS 越小,每个 epoch 的更新次数就越多。
在批量大小变化时,学习率通常如何调整呢?
- 线性缩放法则(经验):若以 GBS0\text{GBS}_0GBS0 与 LR0\text{LR}_0LR0 为基准,则
LReff≈LR0×GBSGBS0 \text{LR}_\text{eff} \approx \text{LR}_0 \times \frac{\text{GBS}}{\text{GBS}_0} LReff≈LR0×GBS0GBS
(SFT 中建议保守调 LR,先稳住 loss 曲线的平滑与下降速度。)
在大多数训练经验中,如果你把有效批量 成倍放大,可以考虑按比例增大学习率。例如:原本的有效批量是 GBS₀,学习率是 LR₀。现在批量变成了原来的两倍,那么学习率也可以变成原来的两倍。这样做的原因是:批量越大,梯度估计的噪声越小,模型可以承受更大的学习率。
但 SFT 阶段建议保守调整,因为 SFT 往往是微调已有模型,更关注稳定收敛而不是追求最快速度,所以不要直接完全按比例放大,而是先试小一点的学习率,看 loss 曲线是否平滑下降。
- Megatron vs Deepspeed 名称差异:
- Megatron 常见显式
global_batch_size。 - Deepspeed / OpenRLHF 可能以
micro_train_batch_size(单卡微批)与train_batch_size(全局)组合表达,语义等价于上式。
- Megatron 常见显式
1.3 学习率(laerning_rate)与调度器 (lr_scheduler_type)
-
SFT 学习率:
LRSFT≈10×LRPretrain \text{LR}_\text{SFT} \approx 10 \times \text{LR}_\text{Pretrain} LRSFT≈10×LRPretrain
学习率设置策略前面已经介绍了,例如预训练 1.5×10−6⇒3×10−51.5\times10^{-6}\Rightarrow 3\times10^{-5}1.5×10−6⇒3×10−5 可作为起点。 -
调度器选择:
常用cosine(余弦退火),也可用linear。constant适合短程/复刻特定实验;exponential较少用在 SFT。 -
热身步数(Warmup):
warmup_steps∈[5%, 10%]×total_steps \text{warmup\_steps} \in [5\%,\,10\%] \times \text{total\_steps} warmup_steps∈[5%,10%]×total_steps
例如总步数 10k,取 500∼1000500\sim1000500∼1000 步。
1.4 正则与 Dropout
- weight_decay:默认 (0.01) 足够稳妥。
- dropout:SFT 阶段一般不开。收益小、拖慢训练;除非明显过拟合且数据极其单一。
1.5 序列长度与分布式切分
序列并行(Sequence Parallelism) 是一种分布式训练的并行方式。
不同于 张量并行(Tensor Parallelism, TP) 按参数维度切分模型,序列并行是按序列长度维度来切分输入数据。
为什么要这么做呢?
当序列特别长(例如 8K、16K、32K token)时,单卡显存可能放不下全部激活值(activations)和中间计算结果。
通过序列并行,把一个长序列拆到多张卡上,每张卡只负责其中一段,就能:
1、降低单卡显存占用(每卡只存自己负责的 token 部分)
2、提升吞吐(多个 GPU 同时处理不同 token)
seq_parallel_size 就是把序列切成多少块并行处理的大小设置。
比如你的输入长度是 8K,seq_parallel_size = 2 就会把它分成两段(每段 4K)分配给不同 GPU 去算。
- max_seq_len:设置成和预训练阶段一致的最大长度,通常是 4K (注意与你的 Base 模型 RoPE/位置编码一致)。
- seq_parallel_size:序列并行的切块大小。对于超长序列和超大模型,配合张量并行使用;并非必要选项,主要用于提升吞吐/突破显存瓶颈,但要求通信良好。
1.6 ZeRO 与 Offload
- zero_stage(Deepspeed):
- ZeRO-2:参数/优化器状态分片,综合吞吐与易用性最佳。
- ZeRO-3:进一步分片参数本体,对带宽要求高,在 NVLink/IB 充足时可扛住,否则训练很慢。
这个在介绍Deepspeed的时候已经介绍,正常选择ZeRO-2即可。(ZeRO-2相当于大家自己留着全套模型,只分工存放优化器和梯度的一部分。而ZeRO-3是大家连模型本体都分开存,想用哪块就去别人那里“借”,所以通信更频繁)
- offload(CPU/NVMe):SFT 阶段一般不建议,I/O 带宽成为瓶颈;除非你就是显存/内存严重不足且能接受明显降速。
(在正常训练中,模型参数、优化器状态、梯度这些数据都放在 GPU 显存 里。如果模型太大、显存放不下,就会用 offload 把一部分数据临时存放到:CPU 内存(RAM)和NVMe 固态硬盘(SSD),用到的时候再把它们搬回 GPU 参与计算。)
为什么不建议?
1、I/O 带宽成瓶颈:从 CPU 或 NVMe 把数据搬到 GPU,要走 PCIe 或硬盘总线,这比显存内部的数据传输慢一个数量级甚至更多。结果是 GPU 一直在等数据,训练速度会明显下降。
2、延迟高:特别是 NVMe offload,需要读写 SSD,延迟可能从微秒级(显存)变成毫秒级甚至更慢。
3、SFT 场景没必要:SFT 通常 batch 较小、数据量有限,本来就不需要特别大的显存支持,所以开 offload 反而是得不偿失。
1.7 梯度检查点(Gradient Checkpointing)
Activation Checkpointing(激活检查点 / 重计算),也叫 Gradient Checkpointing。
- 作用:省显存,用重算换内存。
在训练神经网络时,反向传播需要用到前向传播的中间激活值(activations)。默认情况下,这些激活值会全部保存在显存里,直到对应的梯度计算完毕。如果模型层数多、序列长,这部分激活会占掉大量显存。Activation Checkpointing 的做法是:不保存所有中间激活,只保存**关键节点(检查点)**的激活。反向传播时,需要的激活如果没保存,就重新跑一遍前向计算来生成。这样用额外计算换显存节省。
-
常见结论:计算开销 ≈2×\approx 2\times≈2× 前向;理论上可将中间激活内存从
O(N) 降到 O(N)(分段策略) O(N) \;\;\text{降到}\;\; O(\sqrt{N}) \quad (\text{分段策略}) O(N)降到O(N)(分段策略)
甚至
O(logN)(树状策略) O(\log N) \quad (\text{树状策略}) O(logN)(树状策略)
这里 NNN 近似为层数或检查点切分段数的量级(具体与实现相关)。 -
何时开:当显存紧张但仍想维持较大 GBS\text{GBS}GBS/较长
max_seq_len时。若集群算力富余,开它很划算。
2. 一些“影响不大”的参数
-
per_device_train_batch_size:单卡微批,SFT 常设 1 或 2 以降低 OOM 风险。
-
lr_scheduler_type:
cosine更常用;需要可复现实验时考虑linear/constant。
3. 训练技巧与曲线解读
3.1 分渠道监控 loss(channel_loss)
- 不同 task_type 的损失分布差异很大:
- 创作/开放式生成:答案多样的任务,目标分布更“平”,loss 更高是正常的。
- 结构化/固定答案(翻译、抽取、选择)→ loss 更低。
- 建议为主要任务类型分别统计/绘图,观察各自收敛性。
3.2 特殊符号(special_token)loss
在 tokenizer 里,有一些不是自然语言本身,而是用来控制模型行为的特殊符号,例如:开始/结束标记:< bos >, < eos >,分隔符:< sep >, < pad >,任务相关标记:< user >, < assistant >, < system >, 模板内固定的 prompt 结构:比如 SFT 常见的 指令 + 分隔符 + 答案 格式里的固定 token
这些 token 在预训练和微调中,它们的预测也会参与 loss 计算(除非在 label_mask 里屏蔽掉)。
- 训练初期常见 高一点,随后下降很快。 (训练刚开始时,模型对这些特殊符号的位置和用法还没在新数据里重新适应,因此预测准确率较低,loss 会比普通文本 token 高。但它们的模式高度固定(比如每次都 < bos > 开头, < eos > 结尾),只要模型看到几个 batch,就能学会,所以 loss 会快速下降。)
- 若始终偏高,检查:
- tokenizer/模板拼接是否一致; (训练数据里的特殊符号和 tokenizer 定义的 ID 对不上(比如 在数据里写成了别的形式,或者漏加了必要的 token), 可能数据准备时拼接模板用的符号和推理时用的不一致,导致模型“学的是错的格式”)
- prompt 格式化中的特殊 token 是否被错误截断/重复; (在数据生成或预处理时,把 prompt 和答复拼接时,特殊 token 被错误截断、重复或位置错乱。例如 < bos > 被删掉,或者 < sep > 出现了两次)
- label_mask 是否正确(不要把 prompt 指令也当成可学习标签)。(SFT 时我们一般会屏蔽 prompt 指令部分的 loss(label_mask=0),只让模型学回答部分(label_mask=1)。如果 mask 错了,把 prompt 里的特殊 token 也算进 loss,就会让模型被迫学习它们,但这些 token 在 prompt 中本应是固定输入,学它没意义,还会影响 loss 统计)
3.3 初始与最终 loss 的经验范围
- 通用语料 + 采样均衡 条件下的初始 loss(仅经验区间):
- 7B/13B:≈2\approx 2≈2 左右,难数据可到 ≈3\approx 3≈3;
- 72B:1∼21\sim 21∼2。
- 最终 loss:≈0.5\approx 0.5≈0.5 左右常见。
- 更低并不一定更好:从语言建模角度看,过低可能意味着过拟合/过度记忆(“只会说这一句”)。
3.4 异常:loss 持续升高怎么办?
- 先怀疑训练代码/配置,再怀疑数据难度。
- next-token 预测在理论上“总能背下来”,即使数据是无意义随机串,也不应该出现持续上升,最多持平。
- 排查清单(建议顺序):
- label_shift(是否对齐); (next-token 预测时,标签序列应该是输入序列右移一位(预测下一个 token)。如果错位了,比如预测当前 token 或者错了几个位置,模型根本学不到正确关系,loss 会一直飙升)
- label_mask(是否把 prompt 也算入监督标签);
- 学习率过大/无 warmup;
- 混精度溢出(loss 变 NaN 前的抖动);
- 数据/模板拼接错误(样本为空、越界索引等)。
3.5 关于 SFT Packing 的理性看法
Packing在上面也解释过了,这里不作解释。
- 结论:SFT 默认不建议开(尤其是小数据/难样本)。
- 原因(直观解释):
- 非 packing 时,若 batch_size=1\text{batch\_size}=1batch_size=1 且样本很短,padding 占多,但该步的 梯度几乎全来自这个短样本,因此对其拟合更强。
- packing 把多个短样本拼成一个长序列,本步梯度被多样本稀释,短样本拟合强度变弱。
- 影响:对短 query/短答案的训练容易受损,生成的续写/对话在某些基准上会被拉低。
- 例外:大规模数据/高吞吐场景,packing 对泛化通常无损,但依然建议实测对比。
备注:如使用 Llama-Factory,一些“贪心策略/packing 相关默认开关”需手动确认,避免不知情地改变训练行为。
4. 推荐的参数“起点表”
以 7B/13B 级别、4K 序列长度、单任务占比不过度失衡 的通用 SFT 为例(仅起点,务必根据显存/数据分布/吞吐迭代调优)。
epoch: 1(垂域≤1 万条可 3)per_device_train_batch_size: 1(显存允许取 2)gradient_accumulation_steps: 8~32(看吞吐/显存)GBS: 由上式计算,保证steps_per_epoch在数千~上万步区间更稳learning_rate: 3e-5(若 base 较脆、混精度不稳,可降到 1e-5)lr_scheduler_type: cosinenum_warmup_steps: 5%~10% 的total_stepsweight_decay: 0.01dropout: 0zero_stage: 2(弱带宽慎用 3)max_seq_len: 4096(与 Base 模型一致)offload: 关gradient_checkpointing: 开(算力换显存)
5. 参考配置片段
5.1 Transformers TrainingArguments(Deepspeed)
training_args = TrainingArguments(
output_dir="ckpts/sft",
num_train_epochs=1,
per_device_train_batch_size=1,
gradient_accumulation_steps=16,
learning_rate=3e-5,
lr_scheduler_type="cosine",
warmup_ratio=0.06, # ~6%
weight_decay=0.01,
fp16=True, # 或 bf16=True,看硬件
logging_steps=10,
save_steps=1000,
save_total_limit=2,
gradient_checkpointing=True,
deepspeed="ds_zero2.json", # ZeRO-2
max_grad_norm=1.0
)
这一节详细介绍了SFT参数设置和训练技巧。下一节将会介绍SFT时常用的策略。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)