開源 LLM Post-Training 全攻略:從 SFT 到 RLHF,手把手帶你訓練 Qwen

前言: 隨著 Qwen、LLaMA、Mistral 等高品質開源模型的普及,越來越多工程師開始思考:「如何讓這些模型更符合我的業務需求?」本文將系統性地介紹各種 Post-Training 方法,讓你在選擇技術路線前有完整的全局觀。


什麼是 Post-Training?

Post-Training(後訓練)是指在基礎模型(Base Model)預訓練完成後,透過額外的訓練步驟,使模型具備特定能力或行為的過程。

┌─────────────────────────────────────────────────────────────────────┐
│                    LLM 訓練生命週期                                   │
└─────────────────────────────────────────────────────────────────────┘

  Pre-training          Post-Training
  ─────────────         ───────────────────────────────────────
  海量語料               ┌─ SFT(監督式微調)
  自回歸語言建模    →    ├─ RLHF(人類回饋強化學習)
  學習語言結構           ├─ DPO(直接偏好優化)
                         ├─ ORPO(比率偏好優化)
                         ├─ 持續預訓練(Continued Pretraining)
                         └─ 合併/蒸餾(Merge / Distillation)

方法一:監督式微調(SFT — Supervised Fine-Tuning)

是什麼?

SFT 是最直觀的 Post-Training 方法:準備一批「輸入 → 理想輸出」的配對資料,讓模型學習模仿這些示範。

1# SFT 資料格式範例(指令跟隨)
2{
3  "messages": [
4    {"role": "system", "content": "你是一位專業的台灣稅務顧問。"},
5    {"role": "user",   "content": "個人綜合所得稅要怎麼申報?"},
6    {"role": "assistant", "content": "台灣個人綜合所得稅申報步驟如下:\n\n1. **確認申報期間**:每年 5 月 1 日至 5 月 31 日..."}
7  ]
8}

使用 Qwen + HuggingFace TRL 進行 SFT

 1from transformers import AutoModelForCausalLM, AutoTokenizer
 2from trl import SFTTrainer, SFTConfig
 3from datasets import load_dataset
 4import torch
 5
 6# 1. 載入模型(以 Qwen2.5-7B-Instruct 為例)
 7model_name = "Qwen/Qwen2.5-7B-Instruct"
 8tokenizer = AutoTokenizer.from_pretrained(model_name)
 9model = AutoModelForCausalLM.from_pretrained(
10    model_name,
11    torch_dtype=torch.bfloat16,
12    device_map="auto"
13)
14
15# 2. 準備資料集
16dataset = load_dataset("json", data_files="my_sft_data.jsonl")
17
18# 3. 設定訓練參數
19config = SFTConfig(
20    output_dir="./qwen-sft-output",
21    num_train_epochs=3,
22    per_device_train_batch_size=2,
23    gradient_accumulation_steps=8,
24    learning_rate=2e-5,
25    warmup_ratio=0.1,
26    lr_scheduler_type="cosine",
27    bf16=True,
28    logging_steps=10,
29    save_steps=500,
30    max_seq_length=2048,
31)
32
33# 4. 啟動訓練
34trainer = SFTTrainer(
35    model=model,
36    train_dataset=dataset["train"],
37    args=config,
38)
39trainer.train()

搭配 LoRA 節省記憶體

全參數 SFT 對 GPU 記憶體要求極高,LoRA(Low-Rank Adaptation)是最常見的解法:

 1from peft import LoraConfig, get_peft_model, TaskType
 2
 3lora_config = LoraConfig(
 4    task_type=TaskType.CAUSAL_LM,
 5    r=16,                    # Rank:越高能力越強,但記憶體也越多
 6    lora_alpha=32,           # 縮放係數
 7    target_modules=[         # 對哪些層做 LoRA
 8        "q_proj", "k_proj", "v_proj",
 9        "o_proj", "gate_proj", "up_proj", "down_proj"
10    ],
11    lora_dropout=0.05,
12    bias="none",
13)
14
15model = get_peft_model(model, lora_config)
16model.print_trainable_parameters()
17# trainable params: 83,886,080 || all params: 7,615,832,064
18# trainable%: 1.10%  ← 只需訓練 1% 的參數!

優缺點分析

面向評估
優點直觀易懂;資料準備門檻低;效果穩定可預期
缺點需要大量高品質標記資料;容易過擬合示範風格;無法學習「哪個更好」的概念
適用場景領域知識注入、風格調整、指令遵從能力強化
資料需求1,000 ~ 100,000 筆高品質配對
硬體需求7B 模型:LoRA 約需 16GB VRAM;全參數約需 80GB+
訓練時長LoRA 7B 模型:數小時(A100 × 1)

方法二:人類回饋強化學習(RLHF)

是什麼?

RLHF(Reinforcement Learning from Human Feedback)是 ChatGPT 成功背後的核心技術。訓練流程分三階段:

RLHF 完整流程:

Step 1: SFT(監督式微調)
  Base Model → SFT → SFT Model

Step 2: Reward Model 訓練
  人工標記偏好資料(A vs B 哪個更好?)
  → 訓練 Reward Model(RM)

Step 3: PPO 強化學習
  SFT Model + Reward Signal → PPO 優化
  → 讓模型輸出獲得更高 Reward 的回應

  ┌──────────┐    prompt    ┌─────────────┐
  │ PPO 模型 │ ──────────→ │  Response   │
  └──────────┘             └──────┬──────┘
       ↑                          │ score
  policy update                   ↓
       └──────────── Reward Model ─┘

偏好資料格式範例

1# Reward Model 訓練資料
2{
3  "prompt": "解釋量子糾纏",
4  "chosen": "量子糾纏是指兩個粒子的量子態無論相距多遠都保持關聯...\n(詳細、正確的解釋)",
5  "rejected": "量子糾纏就是兩個東西連在一起。\n(簡陋、不夠準確的回答)"
6}

使用 TRL 的 PPO 訓練

 1from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead
 2from transformers import pipeline
 3
 4# Reward function(可用現成的 reward model)
 5reward_model = pipeline(
 6    "text-classification",
 7    model="OpenAssistant/reward-model-deberta-v3-large-v2"
 8)
 9
10def compute_reward(response_texts):
11    results = reward_model(response_texts)
12    return [torch.tensor(r["score"]) for r in results]
13
14# PPO 訓練設定
15ppo_config = PPOConfig(
16    model_name="Qwen/Qwen2.5-7B-Instruct",
17    learning_rate=1.41e-5,
18    batch_size=16,
19    mini_batch_size=4,
20    gradient_accumulation_steps=4,
21    optimize_cuda_cache=True,
22    early_stopping=True,
23    target_kl=0.1,        # KL 散度上限,防止模型偏離太遠
24)
25
26ppo_trainer = PPOTrainer(
27    config=ppo_config,
28    model=model,
29    ref_model=ref_model,  # 參考模型(SFT 模型的凍結副本)
30    tokenizer=tokenizer,
31)

優缺點分析

面向評估
優點效果上限最高;可學習細緻的人類偏好;OpenAI ChatGPT 的成功驗證
缺點訓練複雜(三階段);需要 PPO 穩定性調校;容易出現 reward hacking;基礎設施複雜
適用場景追求最高對話品質;需要嚴格安全對齊;有充足工程資源的團隊
資料需求偏好資料 10,000~100,000 筆
硬體需求需同時運行 SFT 模型 + RM 模型,GPU 需求 2~4x SFT
訓練時長7B 模型:數天(A100 × 4-8)

方法三:直接偏好優化(DPO — Direct Preference Optimization)

是什麼?

DPO 是 2023 年提出的革命性方法——它把 RLHF 的複雜三步驟,化簡成一個監督學習目標,不需要單獨的 Reward Model,也不需要 PPO。

RLHF vs DPO:

RLHF:Base → [SFT] → [訓練RM] → [PPO] → Final Model
                         複雜!
DPO:  Base → [SFT] → [DPO Loss] → Final Model
                      ↑
              直接用偏好資料計算 loss,跳過中間環節

DPO 的數學直覺

DPO 直接優化這個目標:增加 chosen 回應的對數機率,同時降低 rejected 回應的對數機率,並用 KL 散度限制模型不要偏離參考模型太遠。

 1from trl import DPOTrainer, DPOConfig
 2
 3# DPO 資料格式(與 RLHF 偏好資料相同)
 4dpo_dataset = [
 5    {
 6        "prompt": "如何提升程式碼可讀性?",
 7        "chosen": "提升可讀性的方法包括:\n1. 使用有意義的變數名稱...",
 8        "rejected": "寫好一點就行了。"
 9    },
10    # ...更多資料
11]
12
13# DPO 訓練(比 PPO 簡單很多!)
14dpo_config = DPOConfig(
15    output_dir="./qwen-dpo-output",
16    num_train_epochs=3,
17    per_device_train_batch_size=2,
18    gradient_accumulation_steps=8,
19    learning_rate=5e-7,          # DPO 通常用更小的 LR
20    beta=0.1,                    # KL 懲罰係數,越大越保守
21    bf16=True,
22    loss_type="sigmoid",         # 原始 DPO loss
23)
24
25dpo_trainer = DPOTrainer(
26    model=model,
27    ref_model=ref_model,         # 參考模型(SFT 後的凍結版)
28    args=dpo_config,
29    train_dataset=dpo_dataset,
30    tokenizer=tokenizer,
31)
32dpo_trainer.train()

DPO 變體一覽

DPO 家族:

┌─────────────────────────────────────────────────────────────────┐
│ IPO(Identity PO)   - 解決 DPO 過擬合問題                     │
│ KTO(Kahneman-Tversky)- 不需要成對資料,只需單筆偏好標記      │
│ ORPO                 - 不需要參考模型(更省記憶體)             │
│ SimPO               - 更穩定,移除 reference model 依賴        │
│ CPO(Contrastive PO)- 結合對比學習                            │
└─────────────────────────────────────────────────────────────────┘

優缺點分析

面向評估
優點比 RLHF 簡單得多;不需要獨立 RM;穩定性好;效果接近 PPO
缺點仍需高品質偏好資料;需要參考模型(多佔一份 VRAM);偶爾不如 PPO
適用場景想做偏好對齊但工程資源有限;中小型團隊首選對齊方法
資料需求偏好對(chosen/rejected)5,000~50,000 筆
硬體需求7B + LoRA:約 24~40GB VRAM(需同時跑參考模型)
訓練時長7B 模型:4~12 小時(A100 × 1-2)

方法四:ORPO(Odds Ratio Preference Optimization)

是什麼?

ORPO 是 2024 年提出的更激進簡化——它完全不需要參考模型,把 SFT 和偏好學習合在一個步驟完成。

 1from trl import ORPOTrainer, ORPOConfig
 2
 3# ORPO 最大優勢:不需要 ref_model!
 4orpo_config = ORPOConfig(
 5    output_dir="./qwen-orpo-output",
 6    num_train_epochs=3,
 7    per_device_train_batch_size=2,
 8    gradient_accumulation_steps=8,
 9    learning_rate=8e-6,
10    beta=0.1,              # ORPO 中的 λ(odds ratio 係數)
11    bf16=True,
12    max_length=1024,
13    max_prompt_length=512,
14)
15
16orpo_trainer = ORPOTrainer(
17    model=model,           # 注意:不需要 ref_model!
18    args=orpo_config,
19    train_dataset=dataset,
20    tokenizer=tokenizer,
21)
22orpo_trainer.train()

優缺點分析

面向評估
優點最省記憶體的偏好訓練方法;訓練流程最簡單;效果實驗上競爭力強
缺點較新,社群驗證案例相對少;部分任務不如 DPO
適用場景GPU 資源有限;快速實驗偏好對齊
硬體需求比 DPO 省 30~40% VRAM

方法五:持續預訓練(Continued Pre-Training / Domain Adaptive Pre-Training)

是什麼?

持續預訓練是在 Base Model 上,用大量未標記的領域語料繼續以語言建模目標訓練,注入領域知識。

適用場景:

SFT  → 教模型「怎麼做」(行為)
CPT  → 教模型「知道什麼」(知識)

例如:
- 法律 CPT → 讓模型吸收大量判決書、法條
- 醫療 CPT → 讓模型吸收醫學文獻、病歷格式
- 金融 CPT → 讓模型吸收財報、研究報告
 1from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling
 2
 3# CPT 資料準備(純文本,不需要標記)
 4# 例:10GB 的台灣法律文件
 5def tokenize_function(examples):
 6    return tokenizer(
 7        examples["text"],
 8        truncation=True,
 9        max_length=2048,
10        return_special_tokens_mask=True
11    )
12
13# 語言建模目標(Next Token Prediction)
14data_collator = DataCollatorForLanguageModeling(
15    tokenizer=tokenizer,
16    mlm=False,   # Causal LM(不是 masked LM)
17)
18
19training_args = TrainingArguments(
20    output_dir="./qwen-cpt-output",
21    num_train_epochs=1,              # CPT 通常 epoch 較少
22    per_device_train_batch_size=4,
23    gradient_accumulation_steps=8,
24    learning_rate=1e-5,              # 比 SFT 更小的 LR,防止遺忘
25    warmup_ratio=0.05,
26    bf16=True,
27    dataloader_num_workers=4,
28)
29
30trainer = Trainer(
31    model=model,
32    args=training_args,
33    train_dataset=tokenized_dataset,
34    data_collator=data_collator,
35)
36trainer.train()

優缺點分析

面向評估
優點可注入海量領域知識;無需標記資料;提升基礎語言理解能力
缺點容易導致「災難性遺忘」;需要大量語料;不直接改變行為模式
適用場景垂直領域知識注入(法律、醫療、金融);模型不熟悉的語言/方言強化
資料需求數 GB ~ 數百 GB 未標記語料
硬體需求全參數:80GB+ VRAM;LoRA 版本:16~24GB
訓練時長依語料量,可能需數天到數週

方法六:模型合併(Model Merging)

是什麼?

模型合併是一種不需要任何訓練的「後訓練」方式——直接在參數空間對多個微調模型進行合併,取長補短。

 1# 使用 mergekit 進行模型合併
 2# pip install mergekit
 3
 4# mergekit YAML 設定(SLERP 方法)
 5merge_config = """
 6models:
 7  - model: Qwen/Qwen2.5-7B-Instruct
 8    parameters:
 9      weight: 0.5
10  - model: your-org/qwen-7b-coding-finetuned
11    parameters:
12      weight: 0.3
13  - model: your-org/qwen-7b-chinese-finetuned
14    parameters:
15      weight: 0.2
16
17merge_method: linear    # 可選:linear, slerp, ties, dare_ties
18
19dtype: bfloat16
20"""
21
22# 執行合併
23# mergekit-yaml merge_config.yaml ./merged-model --copy-tokenizer

主流合併方法比較

┌─────────────────────────────────────────────────────────────────┐
│ 合併方法         說明                           適用場景        │
├─────────────────────────────────────────────────────────────────┤
│ Linear Merge   線性加權平均                    快速實驗         │
│ SLERP          球面線性插值(更平滑)           兩模型合併       │
│ TIES           解決參數衝突問題                 多模型合併       │
│ DARE+TIES      隨機丟棄冗餘參數後 TIES 合併    最佳多模型合併   │
│ Task Vectors   方向性任務向量操作              能力增減控制      │
└─────────────────────────────────────────────────────────────────┘

優缺點分析

面向評估
優點零訓練成本;可快速實驗組合;社群有大量開源合併模型可參考
缺點效果不穩定;難以預期;可能引入不一致行為;無法注入全新知識
適用場景快速原型驗證;組合現有開源微調模型;資源極度有限時
硬體需求只需 CPU/RAM 足以載入模型,無需 GPU
時間成本分鐘級(純計算,無訓練)

方法七:知識蒸餾(Knowledge Distillation)

是什麼?

讓小模型(Student)學習大模型(Teacher)的輸出分佈,在保留部分能力的同時大幅壓縮模型大小。

知識蒸餾流程:

Teacher(大模型):Qwen2.5-72B
    ↓ 生成 soft labels(完整機率分佈)
Student(小模型):Qwen2.5-7B
    ↓ 同時學習 hard labels 和 soft labels

損失函數:
  L = α × CrossEntropy(y, student_output)
    + (1-α) × KL_Divergence(teacher_soft, student_soft)
 1import torch.nn.functional as F
 2
 3def distillation_loss(
 4    student_logits,
 5    teacher_logits,
 6    labels,
 7    temperature=4.0,
 8    alpha=0.7
 9):
10    # Soft loss(學習老師的分佈)
11    soft_student = F.log_softmax(student_logits / temperature, dim=-1)
12    soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
13    kl_loss = F.kl_div(soft_student, soft_teacher, reduction="batchmean")
14    soft_loss = kl_loss * (temperature ** 2)
15
16    # Hard loss(學習正確答案)
17    hard_loss = F.cross_entropy(student_logits, labels)
18
19    return alpha * hard_loss + (1 - alpha) * soft_loss

優缺點分析

面向評估
優點可壓縮模型大小;保留大模型能力;適合部署資源受限環境
缺點需要訪問 Teacher 模型;實作複雜;效果仍不如 Teacher
適用場景生產環境需要小模型;邊緣設備部署
硬體需求需同時運行 Teacher + Student,記憶體需求較高

全方法橫向比較

┌─────────────────────────────────────────────────────────────────────────────┐
│                        Post-Training 方法全比較矩陣                          │
├────────────────┬──────────┬──────────┬──────────┬──────────┬────────────────┤
│ 方法           │ 資料需求 │ 算力需求 │ 實作複雜度│ 效果上限 │ 最適場景       │
├────────────────┼──────────┼──────────┼──────────┼──────────┼────────────────┤
│ SFT            │ 中(標記)│ 中       │ 低       │ 中高     │ 行為/風格調整  │
│ RLHF/PPO       │ 高(偏好)│ 高       │ 高       │ 最高     │ 頂級對話品質   │
│ DPO            │ 中(偏好)│ 中       │ 中       │ 中高     │ 對齊首選方案   │
│ ORPO           │ 中(偏好)│ 低中     │ 低       │ 中高     │ 資源有限對齊   │
│ CPT            │ 高(無標)│ 高       │ 中       │ -        │ 領域知識注入   │
│ 模型合併        │ 無        │ 極低     │ 低       │ 中       │ 快速原型/實驗  │
│ 知識蒸餾        │ 中        │ 高       │ 高       │ 中       │ 模型壓縮部署   │
└────────────────┴──────────┴──────────┴──────────┴──────────┴────────────────┘

如何選擇適合你的方法?

你的需求是什麼?
│
├── 想讓模型學習特定領域知識(不改行為)
│   └── → 持續預訓練(CPT)
│
├── 想讓模型遵循指令、改變輸出格式
│   └── → SFT(有時 + DPO 做二階段)
│
├── 想讓模型更「有禮貌」、更安全、更符合人類偏好
│   ├── 資源充足 → RLHF/PPO
│   ├── 一般資源 → DPO
│   └── 資源有限 → ORPO
│
├── 想快速驗證組合現有開源模型
│   └── → 模型合併(Model Merging)
│
└── 想把大模型能力壓縮到小模型
    └── → 知識蒸餾

實戰建議與避坑指南

1. 資料品質 » 資料數量

❌ 錯誤做法:收集 100,000 筆低品質資料
✓ 正確做法:精心標記 1,000 筆高品質資料

研究顯示:5,000 筆高品質 SFT 資料
效果 > 50,000 筆品質不一的資料

2. 從 Instruct 模型開始,不從 Base 開始

1# 推薦:從已有指令跟隨能力的模型開始
2model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
3
4# 不建議(需更多資料才能教會指令跟隨)
5# model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B")

3. 評估策略要提前設計

1# 建立評估集(與訓練集獨立)
2eval_prompts = [
3    "請解釋什麼是量子電腦?",
4    "幫我寫一個 Python 排序函數",
5    # ... 覆蓋你的主要使用場景
6]
7
8# 使用 LLM-as-Judge 評估(如 GPT-4 or Claude 打分)
9# 避免完全依賴 Perplexity 這類訓練指標

4. 常見錯誤

錯誤後果解法
LR 設太高模型「遺忘」原有能力先小 LR 實驗再調大
epoch 過多過擬合、回應多樣性降低早停 + 驗證集監控
資料格式不一致模型學到錯誤的 chat template嚴格統一使用模型原始 template
跳過評估只看 loss不知道模型實際有沒有改善訓練中定期做 human eval
沒有設定 reference modelDPO 訓練不穩定確保 ref_model 為 SFT 後的凍結版本

推薦工具生態

訓練框架:
  ├── TRL(HuggingFace)  → SFT、DPO、PPO、ORPO 一站式
  ├── LLaMA-Factory       → 中文社群最友好的微調框架
  ├── Axolotl             → 高度可設定,支援多種方法
  └── Unsloth             → 2x 速度,0.5x 記憶體,LoRA 優化

評估框架:
  ├── lm-evaluation-harness  → 標準 benchmark 跑分
  ├── MT-Bench               → 多輪對話品質評估
  └── OpenCompass            → 中文評估最完善

模型合併:
  └── mergekit               → 支援所有主流合併算法

資料處理:
  ├── Argilla                → 資料標記協作平台
  └── distilabel             → 合成資料生成

總結

Post-Training 不是一條路,而是一個工具箱。對大多數工程師而言:

  1. 起點:先做 SFT,資料品質是關鍵
  2. 進階:加上 DPO 提升偏好對齊,ORPO 更省資源
  3. 知識注入:需要領域知識才考慮 CPT
  4. 快速驗證:模型合併是最便宜的實驗方式
  5. 生產壓縮:部署資源受限時考慮蒸餾

最重要的是:先明確你的目標,再選擇方法。沒有萬能的 Post-Training 方案,適合你業務場景和資源限制的,才是最好的方案。


本文所有程式碼以 HuggingFace TRL 框架為主,以 Qwen2.5 系列模型為範例,但概念同樣適用於 LLaMA、Mistral、Gemma 等其他開源模型。

Yen

Yen

Yen