◥ AI 互動教室 ‹ 生成式 AI 導論
下載 .py 單獨開啟實驗場 ↗ 留言回報
GENAI 進階補充 · B · 微調工具

微調工具實戰:
Unsloth、TRL 與託管微調

主線第 2 課你算過 LoRA 的帳、知道 SFT 在教什麼。 這一課問實際動手的問題:用哪個工具?我們在一張 RTX 4090 上做了一個乾淨的對照—— 同一份 480 筆資料、同一份 4-bit 權重、同一組超參數,TRL+PEFT 和 Unsloth 各跑一次 QLoRA SFT。 選一種資料,按下開跑,重播那兩次訓練:

TRL+PEFT
trl 1.13.0 · peft 0.21.0
step 0/600.0 sloss —
峰值 VRAM —
Unsloth
unsloth 2026.9.11 · trl 0.24.0
step 0/600.0 sloss —
峰值 VRAM —
兩條跑道吃的是同一份資料。先猜:哪一邊先到?loss 會差多少?
實測紀錄:Qwen3-1.7B(4-bit)· 480 筆 · 60 步 · RTX 4090(主機同時跑其他服務)· 2026-09-24。每一步的 loss 與秒數都是實測值,快轉重播。

這一課的實驗場是真的 Python(在你的瀏覽器裡跑,不用安裝任何東西)。 首次載入約需 30–60 秒,正好夠你讀完第 1 節。訓練紀錄與模型的回答都是實測原文, 選單一拉就現場重算——玩壞了重新整理就復原。

01 · 工具地圖 2026-09

先認人:五個工具、三種託管

一句話重點:自己有 GPU、會寫程式 → Unsloth(單卡最省最快)或 TRL+PEFT(Hugging Face 官方、演算法最新); 要設定檔與多卡 → Axolotl;不寫程式 → LLaMA-Factory/Unsloth Studio; 沒有 GPU → 免費的 Colab/Kaggle,或按訓練 token 計費的託管。

這些工具不是互相取代的關係,多數是疊起來的:PEFT 提供 LoRA、TRL 提供訓練器(SFT/DPO/GRPO…), Unsloth 在 TRL 之上把核心運算換成自己的加速版,Axolotl 與 LLaMA-Factory 再把整套包成設定檔或網頁。 2026 年 9 月的現況:

工具它做什麼版本(查證 2026-09-24)適合
TRL+PEFTHugging Face 官方:SFTTrainer、DPOTrainer、GRPOTrainer、KTO、Reward…;PEFT 負責 LoRAtrl 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、DeepSpeed0.19.0(09-10)團隊、要可重現的設定
LLaMA-FactoryWebUI 點選就能訓練,支援 100+ 種模型0.9.5(05-30)不寫程式
torchtunePyTorch 官方的後訓練庫README:2025 年起停止維護別選了(舊教學還會提到它)
Together/Fireworks上傳 JSONL,雲端幫你跑 LoRA/全參數,按訓練 token 計費見第 6 節價目沒有 GPU、不想管機器
TinkerThinking 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、要不要寫程式、做哪種訓練、資料能不能離開公司。

02 · 實測對照

同一份資料,兩個工具:差在哪?

一句話重點:工具不改變模型學到什麼——兩條 loss 曲線疊在一起;改變的是代價: 短資料差距小;資料一長,Unsloth 的峰值 VRAM 少約四成、總時間快 1.6–2.2 倍。

實驗設定(兩邊完全相同,只換工具):

項目設定
模型unsloth/Qwen3-1.7B-bnb-4bit(兩邊載入同一份 NF4 權重)
資料480 筆「客服訊息 → 工單 JSON」,腳本以固定 seed 產生(任務細節見第 3 節)
LoRAr=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 s168–200 s/78–124 s
每步秒數中位數,TRL ÷ Unsloth1.3–1.4 倍1.6–3.3 倍
最後一步 loss0.004–0.005/0.00350.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 當場拒絕(實測原文):

× No solution found when resolving script dependencies: ╰─▶ Because unsloth==2026.9.11 depends on one of: trl>=0.18.2,<0.19.0 trl>0.19.0,<=0.24.0 and you require unsloth==2026.9.11, we can conclude that you require one of: trl>=0.18.2,<0.19.0 trl>0.19.0,<=0.24.0 And because you require trl==1.13.0, we can conclude that your requirements are unsatisfiable.

所以這個實驗其實是兩個環境。兩邊的核心程式長這樣(參考程式,不在課內執行;完整版在文末連結):

# ── TRL+PEFT(trl 1.13)────────────────────────── from peft import LoraConfig from trl import SFTConfig, SFTTrainer trainer = SFTTrainer( model=model, # AutoModelForCausalLM 載入的 4-bit 權重 train_dataset=ds, # {"prompt": [...], "completion": [...]} peft_config=LoraConfig(r=16, lora_alpha=16, target_modules=SEVEN_PROJ), args=SFTConfig(per_device_train_batch_size=8, learning_rate=2e-4, optim="adamw_8bit", gradient_checkpointing=True, bf16=True), ) trainer.train() # ── Unsloth(2026.9.11,底下是 trl 0.24)───────────── from unsloth import FastLanguageModel # 一定要比 transformers/trl 先 import from unsloth.chat_templates import train_on_responses_only model, tok = FastLanguageModel.from_pretrained( "unsloth/Qwen3-1.7B-bnb-4bit", max_seq_length=512, load_in_4bit=True) model = FastLanguageModel.get_peft_model( model, r=16, lora_alpha=16, target_modules=SEVEN_PROJ, use_gradient_checkpointing="unsloth") # ← 梯度檢查點搬到 CPU trainer = SFTTrainer(model=model, processing_class=tok, train_dataset=ds_text, args=SFTConfig(dataset_text_field="text", ...)) # 其餘超參數同上 trainer = train_on_responses_only(trainer, instruction_part="<|im_start|>user\n", response_part="<|im_start|>assistant\n") trainer.train()
03 · 訓練前後

60 步教會了它什麼

一句話重點:SFT 教的是格式與行為。40 則沒看過的訊息:底模只拿到一句話時全錯, 把規則寫滿 132 tokens 對 23 題,微調後只要一句話就對 39 題。

任務是客服最常見的苦工:把客人的一段話,轉成系統吃得下的工單 JSON—— 類別(五選一)、產品型號、急不急、一句摘要。考題是訓練時沒看過的句型,一半的產品型號也是新的。 同一則訊息,三種做法的真實輸出:

客人說:關於 FitBand 5:被重複扣款兩次,麻煩確認。麻煩了,謝謝。

底模+一句話 prompt
```json
{
  "工單類別": "支付問題",
  "產品名稱": "FitBand 5",
  "問題描述": "被重複扣款兩次,麻煩確認。",
  "聯絡人": "使用者",
  "聯絡方式": "電話/郵件(請補充)",
  "狀態": "待處理",
  "備註": "請協助確認扣款情況,謝謝。"
}
```
✗ 整段是 JSON ✗ 欄位 ✗ 類別 ✗ 產品 ✗ 急件
底模+寫滿規則的 prompt
{"category": "billing", "product": "FitBand 5", "urgent": true, "summary": "FitBand 5 被重複扣款兩次,請確認。"}
✓ 整段是 JSON ✓ 欄位 ✓ 類別 ✓ 產品 ✗ 急件
微調後+一句話 prompt
{"category": "billing", "product": "FitBand 5", "urgent": false, "summary": "FitBand 5 被重複扣款兩次"}
✓ 整段是 JSON ✓ 欄位 ✓ 類別 ✓ 產品 ✓ 急件

實測紀錄(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。

04 · 資料格式

messages、chat template、loss masking

一句話重點:三個工具都吃一行一筆的 messages JSONL;工具用模型自己的 chat template 把它攤成一條字串, 並且只對回答的部分算 loss——問題只是題目,不是要背的東西。

本課資料集的一筆長這樣(system+user+assistant,assistant 就是標準答案):

{"messages": [ {"role": "system", "content": "你是客服工單助理。把使用者訊息轉成 JSON 工單。"}, {"role": "user", "content": "我的 ZoomPad 11 螢幕有一條綠線,可以幫忙修嗎?不急,有空再回覆就好。"}, {"role": "assistant", "content": "{\"category\": \"repair\", \"product\": \"ZoomPad 11\", \"urgent\": false, \"summary\": \"ZoomPad 11 螢幕有一條綠線\"}"} ]}

訓練前,工具會用 Qwen3 的 chat template 把它攤平,再決定哪些 token 算 loss。 下面是實測時 TRL 的資料整理器真的吐出來的第一筆(灰=不算 loss 的 61 個 token,綠=要學會寫出來的 45 個):

<|im_start|>system 你是客服工單助理。把使用者訊息轉成 JSON 工單。<|im_end|> <|im_start|>user 我的 ZoomPad 11 螢幕有一條綠線,可以幫忙修嗎?不急,有空再回覆就好。<|im_end|> <|im_start|>assistant <think> </think> {"category": "repair", "product": "ZoomPad 11", "urgent": false, "summary": "ZoomPad 11 螢幕有一條綠線"}<|im_end|>

注意那段 <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 則在建立訓練器時就被擋下(實測):

ValueError: The chat template is not training-compatible (missing prefix-preservation or `{% generation %}` markers) and patching is not supported for this template. Please manually modify the chat template for training.
05 · VRAM 帳

裝得下嗎:權重+訓練狀態+活化值

一句話重點:峰值 VRAM ≈ 權重 + 訓練狀態 + 活化值。QLoRA 把權重壓到約四分之一、 LoRA 把訓練狀態壓到幾乎為零——剩下的活化值跟序列長度成正比,正是 Unsloth 省下來的那一塊。

主線課用公式算過 8B 模型「全參數 90 GB → LoRA 15 GB」。這次換成實測,同一台 4090 量到的峰值:

模型做法TRL+PEFTUnsloth
Qwen3-0.6BQLoRA(4-bit)1.31 GiB1.16 GiB
Qwen3-0.6BQLoRA(4-bit),長資料2.22 GiB1.50 GiB
Qwen3-1.7BQLoRA(4-bit)2.18 GiB2.00 GiB
Qwen3-1.7BQLoRA(4-bit),長資料4.09 GiB2.49 GiB
Qwen3-4BQLoRA(4-bit)3.60 GiB3.40 GiB
Qwen3-4BQLoRA(4-bit),長資料8.16 GiB4.34 GiB
Qwen3-1.7BLoRA(bf16 底模)4.26 GiB3.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 實測點檢查估算準不準。租卡之前,先在這裡算一次。

06 · 託管與免費路徑

沒有 GPU:免費的、付費的各一條路

一句話重點:免費:Colab/Kaggle 的 T4(不保證有卡、有時數上限); 付費:Together、Fireworks、Tinker 按訓練 token 計費。OpenAI 的微調平台已不再開放新用戶。
服務計費(≤16B 模型,查證 2026-09-24)你要做的事
Together AISFT 每百萬訓練 token $0.34–0.38、DPO $0.84–0.94;每個 job 有最低收費($4 起)上傳 JSONL、選模型、開訓
Fireworks AILoRA SFT 每百萬訓練 token $0.50;全參數、DPO 另有價目上傳 JSONL、選模型、開訓
TinkerQwen3-8B 訓練每百萬 token $0.44(取樣、儲存另計)自己寫訓練迴圈,呼叫它的 API
OpenAI—官方文件:不再開放新用戶
Colab/Kaggle免費(T4 16 GB;Colab 單次最長 12 小時、不保證有卡;Kaggle 每週有免費 GPU 時數)開 notebook、按執行;資料會上傳到 Google

帶回家自己跑(免費)——兩條路,都不需要任何 API key 或自架服務:

  1. 最穩:Unsloth 官方維護的免費 notebook(unsloth.ai 的 notebook 列表, 有 Colab 與 Kaggle 版),選 Qwen3 或 Llama 3.2,接上 T4 從頭按到尾——跟本課同一套 FastLanguageModel → SFTTrainer 流程。
  2. 重現本課實驗:把這三支腳本放同一個資料夾,在有 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 低得多,秒數會慢好幾倍(沒實測,不給數字)。

07 · 速查

本課名詞速查卡

TRL+PEFTHugging 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 計費;資料會離開公司。
08 · 實戰

換你動手

LEVEL 1

在實驗場 2️⃣ 切到「長對話紀錄」,把三次實測輪流看一遍:Unsloth 每步快幾倍? 為什麼「峰值 VRAM」三次一模一樣,時間卻每次不同?

LEVEL 2

在 5️⃣ 選 Qwen3-8B、每筆 1024 tokens、batch 8: 免費的 T4(16 GB)用 QLoRA 裝得下嗎?把工具換成 Unsloth 呢?還是要用 TRL 的話,batch 要降到多少?

LEVEL 3

在 4️⃣ 選「Alpaca 舊格式」,直接在框裡把它改寫成 messages 格式,直到驗證器全綠。 再到 3️⃣ 找出微調後唯一答錯的那題:你會補什麼樣的訓練資料來修好它?

卡住了?三題在實驗場最後都有折疊解答——先自己做,再打開對照。

09 · 驗收

情境測驗

離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。

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 撞出來的)。最可能的原因與修法?

File ".../torch/amp/grad_scaler.py", line 294, in _unscale_grads_ torch._amp_foreach_non_finite_check_and_unscale_( NotImplementedError: "_amp_foreach_non_finite_check_and_unscale_cuda" not implemented for 'BFloat16'

實測拆開來看:訓練器建好後,可訓練的 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,只換了模型名稱,其餘照抄。建立訓練器時噴了這個(實測原文)。怎麼回事?

trainer = train_on_responses_only( trainer, instruction_part="<|start_header_id|>user<|end_header_id|>\n\n", response_part="<|start_header_id|>assistant<|end_header_id|>\n\n", ) ValueError: Unsloth: train_on_responses_only masked every label to -100 in train_dataset, so there is nothing to train on. The response marker '<|start_header_id|>assistant<|end_header_id|>\n\n' was not found in any sample - check that instruction_part and response_part match your chat template.

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 最擅長的行為類任務,不需要動全部參數。

Python 環境載入中(首次約 30–60 秒)…讀完第 1 節它就好了