微調工具實戰:
Unsloth、TRL 與託管微調
主線第 2 課你算過 LoRA 的帳、知道 SFT 在教什麼。 這一課問實際動手的問題:用哪個工具?我們在一張 RTX 4090 上做了一個乾淨的對照—— 同一份 480 筆資料、同一份 4-bit 權重、同一組超參數,TRL+PEFT 和 Unsloth 各跑一次 QLoRA SFT。 選一種資料,按下開跑,重播那兩次訓練:
這一課的實驗場是真的 Python(在你的瀏覽器裡跑,不用安裝任何東西)。 首次載入約需 30–60 秒,正好夠你讀完第 1 節。訓練紀錄與模型的回答都是實測原文, 選單一拉就現場重算——玩壞了重新整理就復原。
先認人:五個工具、三種託管
這些工具不是互相取代的關係,多數是疊起來的:PEFT 提供 LoRA、TRL 提供訓練器(SFT/DPO/GRPO…), Unsloth 在 TRL 之上把核心運算換成自己的加速版,Axolotl 與 LLaMA-Factory 再把整套包成設定檔或網頁。 2026 年 9 月的現況:
| 工具 | 它做什麼 | 版本(查證 2026-09-24) | 適合 |
|---|---|---|---|
| TRL+PEFT | Hugging Face 官方:SFTTrainer、DPOTrainer、GRPOTrainer、KTO、Reward…;PEFT 負責 LoRA | trl 1.13.0(09-10)· peft 0.21.0 | 要最新演算法、要客製流程 |
| Unsloth | 同一套 TRL 訓練器,底下換成自家 Triton 核心、梯度檢查點搬到 CPU、自動 padding-free;另有網頁版 Studio、桌面版 Desktop(2026-08 起 beta) | 2026.9.11(09-23) | 單卡、VRAM 吃緊、長序列 |
| Axolotl | 一份 YAML 描述整個訓練;多卡、FSDP、DeepSpeed | 0.19.0(09-10) | 團隊、要可重現的設定 |
| LLaMA-Factory | WebUI 點選就能訓練,支援 100+ 種模型 | 0.9.5(05-30) | 不寫程式 |
| torchtune | PyTorch 官方的後訓練庫 | README:2025 年起停止維護 | 別選了(舊教學還會提到它) |
| Together/Fireworks | 上傳 JSONL,雲端幫你跑 LoRA/全參數,按訓練 token 計費 | 見第 6 節價目 | 沒有 GPU、不想管機器 |
| Tinker | Thinking Machines 的訓練 API:你在本機寫訓練迴圈(forward_backward、optim_step、sample),它出 GPU | 按 token 計費 | 要自訂 SFT/RL 演算法、又沒有叢集 |
| OpenAI fine-tuning | 上傳資料、微調 OpenAI 的模型;RFT 只支援 o4-mini | 官方文件:平台逐步收掉 | 不再開放新用戶 |
來源:PyPI/GitHub releases、各工具官方文件與價目頁,查證日 2026-09-24。
四個問題就能選出起點:手上有什麼 GPU、要不要寫程式、做哪種訓練、資料能不能離開公司。
同一份資料,兩個工具:差在哪?
實驗設定(兩邊完全相同,只換工具):
| 項目 | 設定 |
|---|---|
| 模型 | unsloth/Qwen3-1.7B-bnb-4bit(兩邊載入同一份 NF4 權重) |
| 資料 | 480 筆「客服訊息 → 工單 JSON」,腳本以固定 seed 產生(任務細節見第 3 節) |
| LoRA | r=16、α=16、掛 q/k/v/o+gate/up/down 七個投影 → 可訓練 17,432,576 參數(約 1%) |
| 訓練 | 學習率 2e-4、batch 8、1 epoch=60 步、8-bit AdamW、梯度檢查點、只對回答算 loss |
| 硬體 | RTX 4090 24 GB;主機同時跑其他服務,本實驗每個行程限用 9.5 GB |
結果(每個情境各跑三次;峰值 VRAM 每次幾乎一樣,時間會飄,所以寫範圍):
| 短訊息(約 100 tokens/筆) | 長對話紀錄(約 1,180 tokens/筆) | |
|---|---|---|
| 峰值 VRAM(TRL → Unsloth) | 2.18 → 2.00 GiB(−8%) | 4.09 → 2.49 GiB(−39%) |
| 60 步總時間(TRL/Unsloth) | 21–23 s/18–21 s | 168–200 s/78–124 s |
| 每步秒數中位數,TRL ÷ Unsloth | 1.3–1.4 倍 | 1.6–3.3 倍 |
| 最後一步 loss | 0.004–0.005/0.0035 | 0.003–0.004/0.003 |
換成大一點的 Qwen3-4B、同樣的長資料(跑 20 步量峰值),差距更明顯:TRL 8.16 GiB、Unsloth 4.34 GiB——省了將近一半。 這張 4090 同時還跑著別的服務(GPU 使用率約 45%),所以秒數只看比例與量級。
為什麼長資料才拉開差距?Unsloth 做了三件 TRL 預設沒做的事:padding-free(把一個 batch 的樣本接成一條,不浪費算力在填充符號上)、 把梯度檢查點搬到 CPU(log 裡那句 smartly offload gradients)、以及自己寫的 Triton 核心。 序列越長,活化值越大,這些優化越有地方發揮;每筆才 100 tokens 時活化值本來就小,沒什麼可省—— Unsloth 每步還是快約 1.3 倍,但它第一步要暖機(約 1–2.7 秒),60 步總時間只差 3–27%。 TRL 自己也有 padding_free 選項,但實測直接打開會被擋下(要搭 packing,或拿掉 max_length), 還會警告只有 Flash Attention 系列的實作可靠支援——Unsloth 則是預設都幫你處理好了。
代價是版本鎖:Unsloth 2026.9.11 把 trl 釘在 ≤0.24.0、transformers 釘在 ≤5.5.0。 想同時要「最新的 TRL」和「Unsloth」,uv 當場拒絕(實測原文):
所以這個實驗其實是兩個環境。兩邊的核心程式長這樣(參考程式,不在課內執行;完整版在文末連結):
60 步教會了它什麼
任務是客服最常見的苦工:把客人的一段話,轉成系統吃得下的工單 JSON—— 類別(五選一)、產品型號、急不急、一句摘要。考題是訓練時沒看過的句型,一半的產品型號也是新的。 同一則訊息,三種做法的真實輸出:
客人說:關於 FitBand 5:被重複扣款兩次,麻煩確認。麻煩了,謝謝。
```json
{
"工單類別": "支付問題",
"產品名稱": "FitBand 5",
"問題描述": "被重複扣款兩次,麻煩確認。",
"聯絡人": "使用者",
"聯絡方式": "電話/郵件(請補充)",
"狀態": "待處理",
"備註": "請協助確認扣款情況,謝謝。"
}
```{"category": "billing", "product": "FitBand 5", "urgent": true, "summary": "FitBand 5 被重複扣款兩次,請確認。"}{"category": "billing", "product": "FitBand 5", "urgent": false, "summary": "FitBand 5 被重複扣款兩次"}實測紀錄(Qwen3-1.7B 4-bit、貪婪解碼、2026-09-24);微調後 TRL 與 Unsloth 兩個模型對這題的輸出一字不差。
40 題的總成績:底模+一句話 0 題全對(它自己發明「工單類別」「處理人」「創建時間」這些欄位,還混進簡體字); 寫滿規則 23 題(6 題把 JSON 包進 ```json 框、程式直接讀會失敗; 急件判錯 11 題,全是把不急的判成急);微調後 39 題,而且每次呼叫的 system prompt 從 132 tokens 縮成 17 tokens。 兩個工具練出來的模型答對的題目完全一樣——它們學到的是同一件事。
唯一答錯的那題也很有教育意義:「請問 MeshLink AX 5G 訊號消失是正常的嗎?」——型號後面緊接著「5G」, 模型把它當成型號的一部分。沒看過的產品+模糊的邊界,正是資料該補的地方。 也提醒一件主線課講過的事:微調教的是行為,要它記住你家的產品清單,還是交給 RAG。
messages、chat template、loss masking
本課資料集的一筆長這樣(system+user+assistant,assistant 就是標準答案):
訓練前,工具會用 Qwen3 的 chat template 把它攤平,再決定哪些 token 算 loss。 下面是實測時 TRL 的資料整理器真的吐出來的第一筆(灰=不算 loss 的 61 個 token,綠=要學會寫出來的 45 個):
注意那段 <think>…</think> 空殼:Qwen3 的 template 對最後一則回答自動補上, 推論時用 enable_thinking=False 也會補同一段——訓練與推論的字串要長得一樣,模型才不會錯亂。 每個模型家族的標記都不同(Qwen 是 <|im_start|>assistant,Llama 3 是 <|start_header_id|>assistant<|end_header_id|>),抄錯就會出事。 TRL 1.13 的 assistant_only_loss=True 對 Qwen3 可以直接用,換成 Llama 3.2 則在建立訓練器時就被擋下(實測):
裝得下嗎:權重+訓練狀態+活化值
主線課用公式算過 8B 模型「全參數 90 GB → LoRA 15 GB」。這次換成實測,同一台 4090 量到的峰值:
| 模型 | 做法 | TRL+PEFT | Unsloth |
|---|---|---|---|
| Qwen3-0.6B | QLoRA(4-bit) | 1.31 GiB | 1.16 GiB |
| Qwen3-0.6B | QLoRA(4-bit),長資料 | 2.22 GiB | 1.50 GiB |
| Qwen3-1.7B | QLoRA(4-bit) | 2.18 GiB | 2.00 GiB |
| Qwen3-1.7B | QLoRA(4-bit),長資料 | 4.09 GiB | 2.49 GiB |
| Qwen3-4B | QLoRA(4-bit) | 3.60 GiB | 3.40 GiB |
| Qwen3-4B | QLoRA(4-bit),長資料 | 8.16 GiB | 4.34 GiB |
| Qwen3-1.7B | LoRA(bf16 底模) | 4.26 GiB | 3.93 GiB |
| Qwen3-0.6B | 全參數(bf16+AdamW) | 6.26 GiB | — |
PyTorch 峰值 reserved(GiB);batch 8、每筆約 100 tokens,「長資料」每筆約 1,180 tokens;RTX 4090,2026-09-24。 另一個實測:Qwen3-1.7B 全參數微調在 9.5 GB 的額度內直接 OOM。
實驗場 5️⃣ 把這些實測點拿來現場擬合一個估算公式,推到 8B、14B、32B、70B, 並畫出 T4(16 GB)、RTX 4090(24 GB)、A100(80 GB)三條線—— 還會拿沒參與擬合的 4B 實測點檢查估算準不準。租卡之前,先在這裡算一次。
沒有 GPU:免費的、付費的各一條路
| 服務 | 計費(≤16B 模型,查證 2026-09-24) | 你要做的事 |
|---|---|---|
| Together AI | SFT 每百萬訓練 token $0.34–0.38、DPO $0.84–0.94;每個 job 有最低收費($4 起) | 上傳 JSONL、選模型、開訓 |
| Fireworks AI | LoRA SFT 每百萬訓練 token $0.50;全參數、DPO 另有價目 | 上傳 JSONL、選模型、開訓 |
| Tinker | Qwen3-8B 訓練每百萬 token $0.44(取樣、儲存另計) | 自己寫訓練迴圈,呼叫它的 API |
| OpenAI | — | 官方文件:不再開放新用戶 |
| Colab/Kaggle | 免費(T4 16 GB;Colab 單次最長 12 小時、不保證有卡;Kaggle 每週有免費 GPU 時數) | 開 notebook、按執行;資料會上傳到 Google |
帶回家自己跑(免費)——兩條路,都不需要任何 API key 或自架服務:
- 最穩:Unsloth 官方維護的免費 notebook(unsloth.ai 的 notebook 列表, 有 Colab 與 Kaggle 版),選 Qwen3 或 Llama 3.2,接上 T4 從頭按到尾——跟本課同一套 FastLanguageModel → SFTTrainer 流程。
- 重現本課實驗:把這三支腳本放同一個資料夾,在有 NVIDIA GPU 的環境跑
(資料產生器、
Unsloth 版、
TRL+PEFT 版):
pip install uv uv run --script spike_genai_finetune_unsloth.py --out r_unsloth.json uv run --script spike_genai_finetune.py --eval-base --out r_trl.json
誠實說明驗證範圍:兩支腳本在 RTX 4090(驅動 580、CUDA 13)上完整跑通,也用 --fp16 強制走 T4 的精度路徑各跑過一次(T4 是 Turing 架構,沒有原生 bf16;Ampere 以後的卡才有)—— TRL 版第一次走這條路就撞了一個精度錯誤(測驗 Q2 就是它),腳本已修正,修正後同樣 39/40;Unsloth 版一次就過。 沒有在 Colab/Kaggle 的 T4 上實跑:平台預裝的 torch 與驅動版本會變,腳本自建的環境裝不起來時,改走第 1 條路。 T4 的算力比 4090 低得多,秒數會慢好幾倍(沒實測,不給數字)。
本課名詞速查卡
| TRL+PEFT | Hugging Face 官方的訓練器(SFT/DPO/GRPO…)+ LoRA 實作——演算法最新、最通用的起點。 |
| Unsloth | 同一套 TRL 訓練器換上加速核心——單卡最省最快;代價是版本被它釘住。 |
| Axolotl/LLaMA-Factory | 把整套訓練包成 YAML 設定檔/網頁介面——要可重現、或不寫程式時用。 |
| chat template | 把 messages 攤成一條字串的模型專屬格式;訓練與推論必須一致。 |
| loss masking | 只對回答算 loss(問題的 labels 設 -100)——TRL 的 prompt/completion、Unsloth 的 train_on_responses_only。 |
| padding-free | 把一個 batch 的樣本接成一條,不在填充符號上浪費算力——長短不一的資料省最多。 |
| 峰值 VRAM | 權重+訓練狀態+活化值;QLoRA 壓權重、LoRA 壓狀態、Unsloth 壓活化值。 |
| 託管微調 | Together/Fireworks/Tinker 按訓練 token 計費;資料會離開公司。 |
換你動手
在實驗場 2️⃣ 切到「長對話紀錄」,把三次實測輪流看一遍:Unsloth 每步快幾倍? 為什麼「峰值 VRAM」三次一模一樣,時間卻每次不同?
在 5️⃣ 選 Qwen3-8B、每筆 1024 tokens、batch 8: 免費的 T4(16 GB)用 QLoRA 裝得下嗎?把工具換成 Unsloth 呢?還是要用 TRL 的話,batch 要降到多少?
在 4️⃣ 選「Alpaca 舊格式」,直接在框裡把它改寫成 messages 格式,直到驗證器全綠。 再到 3️⃣ 找出微調後唯一答錯的那題:你會補什麼樣的訓練資料來修好它?
卡住了?三題在實驗場最後都有折疊解答——先自己做,再打開對照。
情境測驗
離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。
Q1 情境題
你們要用 Qwen3-4B 微調客服模型,每筆訓練資料都帶著上千 tokens 的歷史對話;手上只有一張 24 GB 的卡,資料不能上雲。最務實的起點是?
本課就量過這個組合:Qwen3-4B、每筆約 1,180 tokens 的長資料,QLoRA 峰值 TRL+PEFT 8.16 GiB、Unsloth 4.34 GiB——單卡、長序列正是 Unsloth 的主場,而且它底下還是同一套 SFTTrainer,換回 TRL 的成本很低。A 的問題是記憶體:4B 全參數光權重加訓練狀態就要約 37 GiB(實驗場 5️⃣ 的估算),24 GB 裝不下;而且「資料長」跟「要動全部參數」沒有關係,LoRA 在格式與行為類的任務上夠用。C 是舊資訊:torchtune 的 README 已寫明 2025 年起停止維護。D 兩個問題:資料不能上雲,而且 OpenAI 官方文件寫著微調平台不再開放新用戶。
Q2 錯誤診斷
你把本課的 TRL+PEFT QLoRA 腳本搬到免費 Colab 的 T4 上,因為 T4 沒有原生 bf16,就把 bf16=True 改成 fp16=True。訓練第一步就噴了這個(實測原文:在 4090 上強制 fp16 撞出來的)。最可能的原因與修法?
實測拆開來看:訓練器建好後,可訓練的 LoRA 張量全是 bfloat16——這份 unsloth/Qwen3-1.7B-bnb-4bit 的量化設定寫著「用 bf16 運算」,LoRA 跟著建成 bf16。fp16 混合精度靠 GradScaler 放大、再縮回梯度,它只處理 fp32(主權重)的梯度,碰到 bf16 就報這個錯。修法是本課腳本現在的做法:4-bit 層的 compute_dtype 改成 fp16、可訓練參數轉成 fp32——修好後同樣答對 39/40;Unsloth 版這兩件事自動做,--fp16 一次就過。B 在 4090 上會過,在 T4 上是另一個坑:T4 沒有原生 bf16,只能靠模擬,慢又容易出錯——錯誤訊息裡的 BFloat16 是線索,不是答案。A 誤會了:QLoRA 就是為小卡設計的,本課 1.7B 峰值才約 2 GiB。D 是症狀相似但原因不同:這不是溢位,是型別對不上,第一步就會發生、跟 batch 無關。
Q3 情境題
同事看了本課的成績單說:「prompt 寫長一點就有 23 題,何必微調?」哪個回應最有根據?
決策要看「錯誤的代價」與「呼叫量」。本課的工單要被程式直接 json.loads,寫滿規則的 prompt 有 6 題因為包了 ```json 框直接讀不進去、11 題把客氣的「麻煩了,謝謝」判成急件——進生產管線就是 17 張錯單。微調後 39/40,而且每次呼叫少送 115 個 token,量一大就是錢。A 忽略了剩下 17 題錯在哪、錯了誰來收;B 太絕對:微調有資料、訓練、維護成本,量小或規則常變時 prompt 更划算,要記住公司知識則是 RAG 的工作;D 與數字不符(23 vs 39)。
Q4 錯誤診斷
你把網路上一份「Unsloth 微調 Llama 3」的範例改成訓練 Qwen3,只換了模型名稱,其餘照抄。建立訓練器時噴了這個(實測原文)。怎麼回事?
loss masking 靠「在攤平的字串裡找到回答從哪裡開始」——那個位置就是模型 chat template 裡的 assistant 標記,而每個模型家族的標記都不一樣。Qwen3 的字串裡根本沒有 <|start_header_id|>,於是每個 token 都被標成 -100(不算 loss),Unsloth 發現沒東西可學就直接擋下。修法是換成 instruction_part="<|im_start|>user\n"、response_part="<|im_start|>assistant\n"(本課的設定),或先用實驗場 4️⃣ 看清楚 template 長什麼樣再寫。C 能跑但有副作用:模型連使用者的問題與 system prompt 都要學著「寫出來」,力氣花在錯的地方;A 跟資料量無關,加多少筆都找不到標記;D 不成立——本課就是用 Unsloth 訓練 Qwen3。
Q5 情境題
醫院要把病歷摘要整理成固定格式,資料不能離開院內網路。院內有一台 16 GB 的 T4 伺服器,想微調 Qwen3-8B,每筆約 1,000 tokens。怎麼做?
先守住「資料不能離開院內」,再算記憶體:實驗場 5️⃣ 的估算,Qwen3-8B、每筆 1,024 tokens、batch 8,QLoRA 用 Unsloth 約 7.8 GiB、用 TRL+PEFT 約 11.3 GiB,都在 T4 的 15 GiB 以內(8B 沒有實測點,這是估算——正式跑之前先用小 batch 跑幾步看峰值)。A 與 B 都是把病歷傳到別人的機器:免費 Colab 一樣是雲端,託管更不用說,價格再便宜都不該選。D 算一下就知道不可能:8B 全參數估算約 80 GiB,是 T4 的五倍;而且「整理成固定格式」是 LoRA 最擅長的行為類任務,不需要動全部參數。