EP.07AI 離線部署系列

Fine-tuning 實戰:LoRA 與 Adapter
加上 ASR 語音辨識基礎與 Whisper 微調

大模型已經很強了,為什麼還需要 Fine-tuning?Adapter 和 LoRA 怎麼做到「少訓練少數參數、效果不輸全量微調」? 最後用 LoRA 微調 Whisper,把 ASR 基礎和 Fine-tuning 技術一次串起來。

Joseph Chen 2026 18 min read LoRA · Adapter · PEFT · Whisper · ASR

「EP.06 我們把模型壓縮到邊緣裝置上。但有時候問題不是模型太大, 而是模型不夠『懂你的行業』。這時候需要的不是壓縮,而是微調。」

Fine-tuning 把通用預訓練模型適配到特定任務。 但全量微調(Full Fine-tuning)對 GPU 記憶體的要求極高, 這催生了 Adapter 和 LoRA 這類參數高效微調(PEFT)方法。 這篇結尾用「LoRA 微調 Whisper」把所有概念串成一個可執行的完整案例。

為什麼需要 Fine-tuning?

預訓練大模型(Foundation Model)已經學到了海量的通用知識, 但「通用」也代表它在你的特定領域不夠精準。

Zero-shot

不給任何例子,直接讓模型回答

無需數據,立即可用

特定領域準確率低

Few-shot

在 Prompt 中給幾個示範例子

不需訓練,靈活

受限於 context 長度,不穩定

Fine-tuning

用你的數據真正更新模型權重

準確率高,行為穩定

需要標注數據和訓練成本

Full Fine-tuning 的記憶體問題

全量微調需要儲存原始權重 + 梯度 + 優化器狀態(Adam 需要一階/二階動量各一份)。 以 LLaMA-2 7B 為例:
· 模型權重:7B × 4 bytes = 28 GB
· 梯度:同樣 28 GB
· Adam 優化器狀態:56 GB
· 合計約 112 GB——一般消費級 GPU(8–24 GB)完全跑不起來。

PEFT:參數高效微調的三條路

PEFT(Parameter-Efficient Fine-Tuning)的核心思想:凍結原始模型大部分權重,只訓練少量新增或指定的參數,就能達到接近全量微調的效果。

三種主流 PEFT 方法對比

方法原理可訓練參數推理開銷
Prefix Tuning在每一層前插入可學習的「軟提示向量」~0.1%有(context 加長)
Adapter在 Transformer 層後插入小型瓶頸網路~1–3%有(串聯計算)
LoRA ⭐把權重更新矩陣分解為兩個低秩矩陣~0.1–1%無(可合併)

為什麼 LoRA 可以合併但 Adapter 不行?

LoRA 的更新量 ΔW = B×A 是一個與原始權重形狀相同的矩陣,可以直接加回去: W' = W₀ + B×A。部署時只需要一個普通的 Linear 層,沒有任何額外模組。

Adapter 的瓶頸網路(Down-project → ReLU → Up-project)是串聯在 Transformer 層後面的獨立模組, 它的輸出不是一個「加法更新」,而是一次非線性變換——無法線性合併回原始權重。 因此推理時每次都要額外執行這個模組,產生固定的推理開銷(延遲 +2–5ms/層)。

Adapter:插入瓶頸層

Adapter(Houlsby et al., 2019)在每個 Transformer 層的 Self-Attention 和 FFN 後面, 各插入一個小型的「瓶頸網路」(Bottleneck)。訓練時只更新這些插入的模組。

Adapter 架構(單層示意)

輸入 x(維度 d)
Transformer 層(凍結)
🔵 Down-project:d → r(r << d)
🔵 非線性激活(ReLU / GELU)
🔵 Up-project:r → d
↓ + Residual(加回輸入 x)
輸出(維度 d,形狀不變)

關鍵設計:瓶頸維度 r

r 通常取 8–64,遠小於 d(768–4096)。
例如 d=768, r=64:
Adapter 參數量 = 2 × (768 × 64) = 98,304
原始層參數量 ≈ 4 × 768² = 2,359,296
只需訓練 4.2% 的額外參數。

為什麼加 Residual?

初始化時 Down-project 用零初始化,Up-project 用隨機初始化。 這讓 Adapter 在訓練開始時輸出接近零,等同於直接 bypass—— 不會破壞預訓練模型的行為,訓練更穩定。

LoRA:低秩分解的優雅方案

LoRA(Low-Rank Adaptation,Hu et al., 2022)從線性代數出發, 提出一個更巧妙的假設:大模型在 fine-tuning 時,權重更新矩陣 ΔW 是低秩的。既然 ΔW 是低秩的,就可以用兩個小矩陣的乘積來近似它。

LoRA 核心公式

W' = W₀ + ΔW = W₀ + B·A

W₀

原始預訓練權重
形狀:d × k
凍結,不更新

A(右矩陣)

形狀:r × k
隨機高斯初始化
訓練中更新

B(左矩陣)

形狀:d × r
零初始化(確保訓練開始時 ΔW = 0)
訓練中更新

參數量對比(以 GPT-3 175B 的某一層為例)

原始 W₀4096 × 4096 = 16,777,216
LoRA A + B(r=8)(4096×8) + (8×4096) = 65,536
壓縮比只需 0.39% 的額外參數

LoRA vs Adapter:關鍵優勢

  • 推理零開銷:W' = W₀ + BA 可以在部署前一次性合併,推理時就是一個普通權重矩陣。
  • 並行 + 快速切換:多個任務各自有一組 BA,共用 W₀,隨時切換不同的「外掛」。

rank r 怎麼選?

  • • r=4–8:極輕量,適合語言風格調整
  • • r=16–32:平衡性好,大多數任務首選
  • • r=64+:接近全量微調效果,參數也更多
  • 通常先用 r=16 實驗,效果不夠再往上調。
使用 HuggingFace PEFT 套用 LoRA
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import get_peft_model, LoraConfig, TaskType

# 1. 載入基礎模型(以 LLaMA 為例)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    torch_dtype=torch.float16,
    device_map="auto",
)

# 2. 設定 LoRA 配置
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=16,                         # rank
    lora_alpha=32,                # scaling factor = alpha / r
    lora_dropout=0.05,
    target_modules=["q_proj", "v_proj"],  # 只加在 Q 和 V 投影矩陣上
    bias="none",
)

# 3. 套用 LoRA(凍結原始權重,只有 LoRA 矩陣可訓練)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.0622%

# 4. 正常訓練...
# trainer = Trainer(model=model, ...)

# 5. 推理前合併(消除推理開銷)
model = model.merge_and_unload()  # W' = W₀ + BA,回到普通模型

QLoRA:在消費級 GPU 上微調 70B 模型

QLoRA(Dettmers et al., 2023)在 LoRA 基礎上再加一層 4-bit 量化, 讓一張 24GB 的 RTX 3090 就能微調 65B 參數的模型。

🔢

NF4 量化

用「Normal Float 4」格式儲存凍結的 W₀(比 INT4 對正態分佈的精度更好)

🗜️

雙重量化

連量化用的 scale 常數也再量化一次,進一步壓縮記憶體

📄

Paged Optimizer

用 NVIDIA 統一記憶體管理,防止梯度計算時的 OOM crash

QLoRA 載入方式(BitsAndBytes)
from transformers import BitsAndBytesConfig
from peft import prepare_model_for_kbit_training

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,   # 雙重量化
    bnb_4bit_quant_type="nf4",        # NF4 格式
    bnb_4bit_compute_dtype=torch.bfloat16,
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-70b-hf",
    quantization_config=bnb_config,
    device_map="auto",
)

# 為 k-bit 訓練做準備(凍結非 LoRA 層、設定 gradient checkpointing)
model = prepare_model_for_kbit_training(model)
# 接著套用 LoRA config,同前...

⚠️ QLoRA 的部署限制:4-bit 模型無法直接合併

一般 LoRA 訓練後可以用 model.merge_and_unload() 把 B×A 合併回 W₀,得到一個普通的全精度模型,方便直接部署。

QLoRA 的 W₀ 是以 NF4 4-bit 格式儲存的——若直接呼叫 merge_and_unload(), 會把 LoRA 更新加到量化後的低精度 W₀ 上,導致精度損失放大。 正確做法是先把 W₀ 反量化回 float16,完成合併後再重新量化; 或以 LoRA adapter 分開部署(不合併),載入時動態套用,保留最大彈性。

語音辨識(ASR)基礎

ASR(Automatic Speech Recognition)把音頻波形轉成文字。 從 HMM 時代到深度學習,再到 Transformer 的 Whisper,整個演進脈絡和 TTS 有很多鏡像關係—— 只是方向相反(TTS:文字→語音;ASR:語音→文字)。

第一代(1980s–2010s)HMM + GMM

把語音拆成音素序列,每個音素用高斯混合模型建模。需要大量人工標注音素邊界,泛化性差。

第二代(2010s)DNN + CTC

用深度神經網路取代 GMM,引入 CTC loss 解決「文字與音頻不等長對齊」問題,不再需要人工對齊。

第三代(2017–)Seq2Seq + Attention(LAS、Transformer)

端到端 Encoder-Decoder 架構,注意力機制自動學習對齊。準確率大幅提升,尤其在長句和口音上。

第四代(2022–)Whisper(大規模弱監督)

用 680,000 小時的網路音頻進行弱監督訓練,同時學習 ASR + 翻譯 + 語言辨識,泛化性極強。

CTC(Connectionist Temporal Classification)是什麼?

語音幀(frame)的數量遠大於文字的數量。例如 1 秒有 100 個音頻幀,但可能只對應 5 個字。 CTC 的做法是引入一個特殊的「blank」token,允許模型輸出比目標文字更長的序列, 再通過去除重複和 blank 來還原原始文字。

CTC 解碼示例:

模型輸出:h h _ e _ l l _ l _ o _ _

步驟一 去 blank:h h e l l l o

步驟二 去重複:h e l o

最終結果:hello

(_ 代表 blank token)

Whisper 架構:Transformer Encoder-Decoder

Whisper(OpenAI, 2022)用的是標準的 Transformer Encoder-Decoder, 和 EP.04 介紹的架構完全相同——差別只在輸入是音頻 Mel 頻譜而非文字 token。

Encoder(理解音頻)

音頻波形
→ Log-Mel Spectrogram (80 mel bins, 30s = 3000 frames)
→ 2× Conv1D(降採樣)
→ Positional Embedding
→ N × Transformer Block
音頻特徵序列(凍結後用於解碼)

Decoder(生成文字)

特殊 token:<|transcribe|> 或 <|translate|>(多任務標記)
→ Token Embedding
→ N × Transformer Block (含 Cross-Attention 對齊音頻)
→ 線性 + Softmax
→ 自回歸生成下一個文字 token
輸出文字

🎯

多任務訓練

ASR(原語言逐字稿)+ 翻譯(翻成英文)+ 語言辨識,用特殊 token 區分任務

🌐

弱監督數據

從網路上收集 680K 小時音頻-文字對,不需要人工精確對齊,用 Encoder-Decoder 自動學習

🌏

多語言支援

99 種語言,包含普通話、台語(有限)。模型大小從 tiny (39M) 到 large-v3 (1.5B)

實戰:LoRA 微調 Whisper 識別特定領域

Whisper 在通用語音上表現很好,但對於特定領域的術語(醫療、法律、工廠用語)辨識率會下降。 用 LoRA 微調只需要少量標注音頻,就能大幅提升領域準確率。

適合用 LoRA 微調 Whisper 的場景

工廠設備維修的語音下單系統(充滿型號、代碼)
醫院病歷語音輸入(醫療術語、藥名)
台語/客語/特定口音的辨識優化
客服電話逐字稿(產品名稱、客訴用語)
LoRA 微調 Whisper 完整流程
from transformers import (
    WhisperForConditionalGeneration, WhisperProcessor,
    Seq2SeqTrainer, Seq2SeqTrainingArguments
)
from peft import get_peft_model, LoraConfig, TaskType
import torch

# ── 1. 載入模型與處理器 ──────────────────────────────────
model = WhisperForConditionalGeneration.from_pretrained(
    "openai/whisper-small",          # 可換成 medium / large
    torch_dtype=torch.float16,
)
processor = WhisperProcessor.from_pretrained("openai/whisper-small")

# ── 2. 套用 LoRA(只加在 Decoder 的 Q/V 投影上)──────────
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type=TaskType.SEQ_2_SEQ_LM,
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 884,736 / 241,734,912 (0.37%)

# ── 3. 準備數據集(假設已有 audio + transcript 對)────────
def preprocess(batch):
    audio = batch["audio"]["array"]
    # 轉換音頻為 Mel 頻譜輸入特徵
    inputs = processor(audio, sampling_rate=16000, return_tensors="pt")
    batch["input_features"] = inputs.input_features[0]
    # Tokenize 文字標籤
    batch["labels"] = processor.tokenizer(batch["sentence"]).input_ids
    return batch

# ── 4. 訓練 ─────────────────────────────────────────────
training_args = Seq2SeqTrainingArguments(
    output_dir="./whisper-lora-finetuned",
    per_device_train_batch_size=8,
    num_train_epochs=3,
    learning_rate=1e-3,          # LoRA 通常用比全量微調更高的 lr
    fp16=True,
    predict_with_generate=True,
)
trainer = Seq2SeqTrainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
)
trainer.train()

# ── 5. 合併 LoRA 權重並儲存 ─────────────────────────────
model = model.merge_and_unload()
model.save_pretrained("./whisper-lora-merged")

實務建議

  • • 數據量:50–200 小時的領域音頻就能有顯著改善(相比全量微調需要更少)
  • • 只微調 Decoder:Encoder 的聲學特徵提取已經很強,Decoder 是語言模型部分,更容易適配新詞彙
  • • 用 whisper-small 或 whisper-medium 作起點;large-v3 效果最好但 GPU 需求也更高
  • • 評估指標:WER(Word Error Rate),越低越好;中文用 CER(Character Error Rate)

本篇重點回顧

🧊Full Fine-tuning 記憶體需求是模型本身的 3–4 倍,消費級 GPU 幾乎跑不起來。
🔌Adapter:在 Transformer 層後插入瓶頸網路(d→r→d),訓練時只更新插入的部分,推理有輕微額外開銷。
🎯LoRA:把 ΔW 分解為 B×A(低秩矩陣乘積),可訓練參數 < 1%,推理前可直接合併回 W₀,無推理開銷。
QLoRA:4-bit 量化 + LoRA,讓消費級 GPU(24GB)能微調 70B 級別的模型。
🎙️ASR 演進:HMM → CTC → Seq2Seq Transformer → Whisper(680K 小時弱監督多任務訓練)。
🔗LoRA × Whisper:只需調整 0.37% 的參數,就能讓 Whisper 學會特定領域術語,是工業落地最實用的組合。

下一篇 EP.08 把視野再拉大——從 TTS 的 Transformer 起點,延伸到 BERT、GPT 系列、RLHF、ChatGPT 的整個 LLM 演進史。

Fine-tuning
LoRA
Adapter
PEFT
QLoRA
ASR
Whisper
EP.07