Chapter 06

SFT:全参 / LoRA / QLoRA / 长上下文

📌 本章基于 hiyouga/LlamaFactory · commit 01398eb18dd4 。 YAML 范例均来自 examples/train_full/ 、 examples/train_lora/ 、 examples/train_qlora/ 。

6.1 SFT 在 LLaMA-Factory 里的四种模式

SFT (Supervised Fine-Tuning) 在框架里通过 finetuning_typequantization_bit 两个字段的组合,可以走出四条路:

模式YAML 关键显存(7B 模型)典型用途
全参 SFT finetuning_type: full + DeepSpeed ≥ 60 GB(bf16) 有大量优质数据、想最大化效果
LoRA SFT finetuning_type: lora, lora_rank: 8 16 GB 主流选项
QLoRA SFT + quantization_bit: 4, quantization_method: bnb 8 GB(7B), 24 GB(70B)单卡 / 大模型
Freeze SFT finetuning_type: freeze, freeze_trainable_layers: 2 居中 只想动模型最后几层

6.2 全参 SFT

仓库自带 examples/train_full/qwen3_full_sft.yaml。其中关键不同于 LoRA 的是:

### method
stage: sft
do_train: true
finetuning_type: full
deepspeed: examples/deepspeed/ds_z3_config.json  # 必须!

全参微调几乎都要 DeepSpeed ZeRO-3 把参数 / 梯度 / 优化器全切,单机 8 卡 Qwen3-4B 才能塞得下。它的 per_device_train_batch_size = 1, gradient_accumulation_steps = 2, learning_rate = 1e-5 —— 比 LoRA 学习率小 10 倍,因为整个模型都在动,太大就会忘掉 pretrain 习得。

注意 全参 SFT 输出的是完整模型save_steps 一个文件就 10+ GB;如果 save_total_limit 不限制,磁盘很快被占满。

6.3 LoRA SFT

第 3 章已经跑过 examples/train_lora/qwen3_lora_sft.yaml。这里讲关键超参的选择直觉

lora_rank(秩 $r$)

LoRA 增量参数量:

$$\#\Delta W = 2 d r,\qquad \text{其中}\ d=\text{hidden size},\ r=\text{lora\_rank}$$

推荐场景rank说明
简单风格迁移 4 / 8 少量数据即可,过拟合风险小
常规行业问答 / 客服 8 / 16 默认起点
多任务、注入大量新知识 32 / 64 接近全参,注意学习率别太高
QLoRA 长上下文 16+ 显存允许时秩越高效果越稳

lora_alpha 与 lora_dropout

LoRA 实际作用到原权重的形式是:

$$y = W x + \tfrac{\alpha}{r}\, B A\, x$$

lora_target

"all" = 找模型里所有 nn.Linear 都加 LoRA。当 GPU 紧时可以缩到:

lora_target: q_proj,v_proj           # 只 attention 的 QV,最便宜
lora_target: q_proj,k_proj,v_proj,o_proj   # 全 attention
lora_target: all                       # 全 attention + MLP,效果最稳,默认推荐

6.4 QLoRA SFT

QLoRA = "4-bit 加载基座" + "fp16/bf16 LoRA"。本质:基座以极低精度只读放在显存里,LoRA 仍用混合精度训练。框架里只要再加两行:

quantization_method: bnb        # bitsandbytes
quantization_bit: 4
quantization_type: nf4
double_quantization: true

显存收益(来自 README 表):

模型LoRA bf16QLoRA 4bit
7B 16 GB 8 GB
13B 32 GB 12 GB
70B 2×A100 80G24 GB(单卡 4090 可跑)
注意 QLoRA 略有性能损失(约 1–2% downstream metric),但显存优势巨大;NF4 + double quantization 是默认搭配,除非你特别懂,否则别改

用已经量化好的模型做 LoRA

examples/train_qlora/llama3_lora_sft_gptq.yaml 演示了另一条路:加载预先用 GPTQ / AWQ 量化好的模型权重,再在上面挂 LoRA:

model_name_or_path: TechxGenus/Meta-Llama-3-8B-Instruct-GPTQ
# 注意:这里没有 quantization_bit,因为模型本身就已经是量化版

这种用法训完不能 merge(量化权重不可逆地损失精度,merge 回去会出错);只能在推理时同样加载 GPTQ 模型 + LoRA 适配器。

6.5 Freeze SFT(局部微调)

finetuning_type: freeze
freeze_trainable_layers: 2          # 只放开 transformer 最后 2 层
freeze_trainable_modules: all       # 这 2 层里所有 module 都可训
# freeze_extra_modules: lm_head     # 额外把 lm_head 也放开

常用于:

6.6 PT:持续预训练 vs SFT

注意 stage: pt 不属于 SFT,但流程极像。区别在数据形态

维度stage=ptstage=sft
数据 长文本(无 prompt/response 划分)对话 / 指令
Loss 计算整段 token 都参与 默认只对 response 计
典型数据 c4_demo、维基百科 alpaca_*sharegpt
template 不需要 必须,且要和基座匹配

仓库自带例子:examples/train_lora/qwen3_lora_pretrain.yaml

6.7 长上下文 SFT

cutoff_len 超过 8K 时,几个字段必须配合修改:

字段建议值
cutoff_len 8192 / 16384 / 32768 / ...
flash_attn fa2
gradient_checkpointing true(不开就 OOM)
per_device_train_batch_size 1(几乎一定是 1)
packing true(让短样本拼起来吃满 cutoff_len)
neat_packing true(packing 时也带 attention mask 不串)
rope_scaling YaRN / linear / dynamic(基座支持的)

典型计算:序列长度从 2048 → 32768,self-attention 显存按 $O(L^2)$ 飙升 256 倍。没有 FlashAttention-2 + gradient checkpointing 不可能跑得动。

6.9 init_adapter():全参 / freeze / LoRA / OFT 的决策入口

本章描述的"全参 / LoRA / QLoRA / 长上下文"四条路,在源码层都走同一个分发函数:src/llamafactory/model/adapter.py:293 init_adapter()。这个函数按 finetuning_type 派发到三条具体路径:

finetuning_type调用 helper关键行为
full _setup_full_tuning()(line 38–55)所有参数 trainable,forbidden_modules 除外(VLM 时默认排除 vision tower)
freeze _setup_freeze_tuning()(line 57–139)freeze_trainable_layers 取末 N / 前 N / 等间距 N 层
loraoft_setup_lora_tuning()(line 141–290)PEFT 注入,最复杂的一条路

三条路共享的 FP32 upcasting 规则

训练侧 trainable 参数默认会被 cast 回 FP32,但下面这些情形会跳过 upcast(adapter.py:313–325):

6.10 LoRA 路径的 11 个分支

_setup_lora_tuning() 看似复杂,可拆成几条互斥分支。读这一节相当于把 LoRA 的"所有 yaml flag 怎么落到代码"看了一遍:

分支YAML flag影响
adapter 合并 adapter_name_or_path: a,b,cline 156–203:除最后一个外的 adapter 都 merge_and_unload 进基座;最后一个变 adapter_to_resume
新建 vs resume create_new_adapterTrue 时即使有 adapter 也跳过 resume,新挂一份
目标模块自动找 lora_target: allfind_all_linear_modules() 扫所有 nn.Linear(vision tower 按 freeze_vision_tower 排除)
LLaMA Pro 跨层扩展 use_llama_pro: truefind_expanded_modules() 把 target 扩到 trainable layers
VLM 排除冲突模块 CompositeModel.lora_conflict_keys 自动定义line 213:例如 Gemma-3N 排除 timm_model
词表扩张 resize_vocab + add_tokensline 222–231:自动把 embedding/lm_head 加进 additional_target
OFT 分支 finetuning_type: oftline 243–250:OFTConfig(r, oft_block_size, module_dropout, modules_to_save)
LoRA 分支 finetuning_type: loraline 233–242:LoraConfig(r, lora_alpha, lora_dropout, use_rslora, use_dora, modules_to_save)
PiSSA 初始化 pissa_init: trueline 264–270:init_lora_weights = "pissa""pissa_niter_X";和量化模型互斥
KTransformers 后端 由 KTransformers 配置触发line 252–257:走 KT 优化路径(LoRA-only)
Unsloth 后端 use_unsloth: trueline 258–262:get_unsloth_peft_model;LoRA-only,无 OFT

6.11 QLoRA 的三档量化路径

QLoRA = finetuning_type: lora + quantization_bit: 4 or 8。看 model/model_utils/quantization.py:82–217 能看到量化字段的三档优先级:

  1. PTQ 模型(line 90–130):模型本身已经 GPTQ / AWQ / AQLM / MXFP4 / FP8 量化好,加载即用。MXFP4 / FP8 与 ZeRO-3 / FSDP 互斥(line 97–101);
  2. 导出时量化(line 132–164):export_quantization_bit: 4 / 8 + export_quantization_dataset,训完一次性 PTQ 出 GPTQ 格式;
  3. on-the-fly 量化(line 166–217):quantization_bit + quantization_method
    • bnb:BitsAndBytes,最常用;4-bit 时 bnb_4bit_quant_storage=compute_dtype 对 FSDP+QLoRA 至关重要(line 178);
    • hqq:HQQ,支持 1–8 bit;与 ZeRO-3/FSDP 互斥;
    • eetq:EETQ,8-bit only,同上互斥。

三档之间互斥:一个模型只能走一种量化路径。"导出时量化"不要和"on-the-fly 量化"同时写。

6.12 全参 SFT 的 forbidden_modules

全参 SFT 用户常误以为"全参 = 字面意义上所有参数都训"。实际 _setup_full_tuning(adapter.py:38–55)还是有 forbidden_modules 排除:

来源典型排除
freeze_vision_tower=True(默认 VLM 训文本侧)vision_tower 全冻
freeze_multi_modal_projector=True 多模态 projector 冻
CompositeModel.lora_conflict_keys 跟 LoRA 冲突的模块(如 Gemma-3N 的 timm_model)

所以 VLM 全参 SFT 默认只训文本侧 + projector;想让 vision tower 也一起训,必须显式 freeze_vision_tower: false

6.13 freeze 微调的三种范围选法

_setup_freeze_tuning(adapter.py:57–139)按 freeze_trainable_layers正负号决定意义:

freeze_trainable_layers意义
正数 N 训末尾 N 层(默认;适合做"加一点知识"的微调)
负数 -N 训开头 N 层(很罕见;做底层概念注入)
use_llama_pro: true等间距取 N 层(layer 数 ÷ N 为步长)

额外可调整:freeze_extra_modules 加额外要训的模块(layer norm / embedding scale 等);freeze_trainable_modules 指定每层内训哪些子模块(all / mlp / self_attn)。

6.14 SFT 调优经验清单

现象多半原因动哪几个字段
Loss 不降 / 卡住 learning rate 太低 / template 不对learning_rate ×2 试;查 template 和基座匹配
Loss 一开始就降到 ~0 训了 prompt 不是 response 检查 train_on_prompt: false;template 是否能区分 user/assistant
Loss 振荡剧烈 batch 太小、LR 太大 调大 gradient_accumulation_steps、减小 learning_rate
过拟合(eval loss 上升) 训练轮数太多 / rank 太大 num_train_epochslora_rank,加 lora_dropout
显存忽大忽小 样本长度不一 packing,开 group_by_length
训完模型变蠢 数据集 / template 与基座不匹配 核对 template 和数据格式;考虑加 preferenceidentity 数据稳住身份