微调这事,七分数据三分参。数据管下限,参数管上限。这篇把 LoRA 微调里最容易踩坑的几个旋钮一次讲清:序列长度、梯度累计、保存频率、warmup,还有数据量到底该喂多少。

序列长度:先决定你省不省显存

--cutoff_len 决定模型每次能吃进多长的文本。超过的部分直接截掉不训练。

序列长度是显存和时间的头号变量,因为自注意力的计算量跟序列长度是平方关系:单位训练步骤的计算量 ∝ 批次大小 × 序列长度²。长度从 1024 拉到 2048,光这一项计算量就翻四倍。

所以短序列的好处很直接:省显存、省时间、不容易 OOM。代价是信息丢失——重要内容如果藏在长文本的后半段,直接被截没了。长文生成、文档摘要这类任务尤其吃亏。

平衡的办法就几条:短文本任务(分类、情感分析)用短序列绰绰有余;长文本任务尽量在句子或段落边界截断,别在句子中间腰斩;超长文本上滑动窗口,让模型分段看完全文。

梯度累计:小步攒着,一起更新

显存不够装大批次时,梯度累计(Gradient Accumulation)是标准解法。思路很简单:每次前向反向算完梯度先不更新参数,攒够 N 步再一次性更新。效果等于把物理小批次拼成一个大批次。

有效批次大小的公式:

$$\text{Effective Batch Size} = \text{batch_size} \times \text{gradient_accumulation_steps} \times \text{world_size}$$

举个例子:--per_device_train_batch_size 1--gradient_accumulation_steps 8--world_size 4,有效批次就是 1 × 8 × 4 = 32。每张卡每步只处理一个样本,实际训练效果相当于一次处理 32 个样本。

好处是显存门槛低、梯度估计更准、训练更稳;代价是每攒一步就多一次反向传播的时间,整体训练变慢。另外注意:累计步数越多,参数更新频率越低,warmup、学习率调度这些都要按"有效步数"重新对齐。

保存频率:存太勤也烧钱

--save_steps 只影响两件事:磁盘 I/O 和中断恢复。

保存本身不影响收敛,但频繁保存会让训练卡在磁盘读写上,保存一次模型,几十秒到几分钟就没了。经验做法是算好账:总步数 1 万、想留 10 个检查点,就每 1000 步存一次。更进一步可以只在验证集分数变好的时候存,省下绝大多数无效 checkpoint。分布式训练里还要注意多节点同步保存,别互相覆盖。

Warmup:先小步走,再放开跑

学习率 warmup 的套路:训练前若干步,学习率从零线性爬升到预设峰值,再进入余弦衰减之类的正常调度。

为什么要这么干?训练初期模型参数还没适应数据分布,一上来就用大学习率,梯度容易爆炸,loss 直接飞走。warmup 给参数一个缓冲期,是防止开局翻车的廉价保险。经验值:warmup 步数取总步数的 10% 左右,--warmup_steps 500 这种写法在万步级训练里很常见。

数据量:质量先于数量

模型能不能泛化,数据质量比数据数量重要得多。

先做去重和清洗:重复样本会让模型过拟合到那几条数据上;乱码、无关内容直接是噪声。再保证多样性:不同主题、不同场景都要覆盖,分层采样保证各类比例合理。

数据量有个粗糙的经验法则:有效样本数 ≈ 模型参数数 / 1000。一个 7 亿参数的模型,对应大约 70 万条有效样本;70 亿参数就是 700 万条。这是非常粗略的估计,具体还看任务难度和数据质量,但拿来当起点估预算够用。

一份能直接用的配置

用 LLaMA-Factory 微调,一套常见的 LoRA 配置长这样:

llamafactory-cli train \
    --stage sft \
    --finetuning_type lora \
    --model_name_or_path /path/to/model \
    --dataset your_dataset \
    --cutoff_len 2048 \
    --learning_rate 0.0001 \
    --num_train_epochs 3.0 \
    --per_device_train_batch_size 2 \
    --gradient_accumulation_steps 4 \
    --lr_scheduler_type cosine \
    --warmup_steps 500 \
    --save_steps 500 \
    --bf16 True \
    --lora_rank 16 --lora_alpha 16 --lora_dropout 0.05 --lora_target all \
    --gradient_checkpointing True \
    --max_grad_norm 1.0 \
    --output_dir /path/to/output

几个要点的优先级:先按显存把序列长度压到能跑,再用梯度累计补有效批次,warmup 和保存频率照经验值配,最后用 nvidia-smi 盯住显存,在验证集上每几百步看一眼 loss 和分数再微调。

微调没有一劳永逸的黄金参数,但上面这几条,能把你的调参起点从"乱试"变成"有方向"。剩下的事,交给数据和耐心。

FIN