EP.06AI 離線部署系列

嵌入式落地實戰
模型壓縮、量化與邊緣部署

VITS 音質再好,也必須壓縮才能跑在智慧穿戴裝置上。 Pruning、Quantization、Knowledge Distillation——三大壓縮技術的原理與取捨,以及 NPU/DSP 部署的實戰眉角。

Joseph Chen 2026 15 min read Model Compression · NPU · Edge AI

「一個 VITS 模型動輒 200MB+,一塊智慧手錶只有 2MB 的 SRAM。 這中間的落差,就是模型壓縮要解決的問題。」

EP.05 我們看完了五大 TTS 模型的演進。但即使是目前最佳的 VITS, 它的模型大小與記憶體需求,對嵌入式裝置來說仍然遙不可及。 這篇是整個 TTS 系列的「工程落地」收尾篇—— 從壓縮技術、硬體加速,到真實部署遇到的坑。

為什麼需要模型壓縮?

邊緣裝置(Edge Device)與雲端伺服器的差距,遠比你想像的大。

規格雲端伺服器 (A100)邊緣 SoC (典型)智慧手錶 MCU
記憶體(RAM)80 GB HBM4–16 GB LPDDR256 KB – 2 MB SRAM
算力(TOPS)312 TFLOPS FP1610–50 TOPS INT80.1–5 TOPS INT8
功耗400 W5–15 W10–100 mW
適合模型大小> 10 GB100 MB – 2 GB< 5 MB
典型裝置AWS / GCP GPURaspberry Pi, JetsonApple Watch, 助聽器

壓縮的目標不只是「縮小」

壓縮有三個維度的目標:模型大小(Flash 佔用)、記憶體峰值(Runtime RAM)、推理延遲(Latency)。 這三個指標彼此關聯但不等價——一個量化後的模型可能大小縮了 4×,但因為使用非硬體友好的運算, 延遲反而變差。

剪枝(Pruning):移除不重要的連接

神經網路中有大量權重接近零,對輸出幾乎沒有貢獻。 剪枝就是找出這些「冗餘權重」並將它們設為零(或直接移除),以降低計算量。

非結構化剪枝(Unstructured)

0.8
0
0.3
0
0.7

灰色 = 被剪掉的權重

  • ✅ 壓縮率高(可剪 90%+)
  • ❌ 產生稀疏矩陣,需要特殊硬體支援才能加速
  • ❌ 一般 CPU/GPU 無法直接加速稀疏運算

結構化剪枝(Structured)

整行/列被移除

  • ✅ 產生密集矩陣,標準 BLAS 即可加速
  • ✅ 直接縮小模型維度,真正降低 FLOPs
  • ❌ 壓縮率較低,精度損失也較大

實務建議:邊緣部署優先選結構化剪枝

除非你的目標硬體有稀疏加速單元(如 NVIDIA A100 Ampere 架構),否則非結構化剪枝在邊緣裝置上幾乎無法帶來速度提升。 結構化剪枝雖然壓縮率較低,但產出的模型可以直接在所有硬體上跑得更快。

量化(Quantization):降低數字精度

量化是目前邊緣部署最常用、效益最高的技術。原理很直觀: 把原本用 FP32(32位元浮點數)儲存的權重,降低到 INT8(8位元整數)甚至 INT4。

量化的效益

模型大小縮小

FP32INT8

2–4×

推理速度提升

FP32INT8

75%

記憶體頻寬節省

FP32INT8

量化的數學

INT8 值 = round( FP32 值 / scale ) + zero_point
FP32 值 ≈ ( INT8 值 - zero_point ) × scale

# scale 和 zero_point 是從校準數據集(calibration dataset)統計出來的

訓練後量化(PTQ)

模型訓練完畢後再量化。只需要少量校準數據(~100 筆),不用重新訓練。 適合快速驗證,但精度損失較大。

model → calibrate → INT8 model

量化感知訓練(QAT)

訓練過程中模擬量化誤差(Fake Quantization),讓模型學會在量化環境下準確推理。 精度損失極小,但需要重新訓練幾個 epoch。

model → fake quant training → INT8 model
PyTorch PTQ 量化(Static Quantization)
import torch
from torch.quantization import quantize_dynamic, prepare, convert

# 方法一:動態量化(Dynamic Quantization)
# 只量化線性層,activation 在運行時動態量化
model_int8 = quantize_dynamic(
    model,
    qconfig_spec={torch.nn.Linear},
    dtype=torch.qint8
)

# 方法二:靜態量化(Static Quantization)需要校準步驟
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
model_prepared = prepare(model)

# 跑一批校準數據
with torch.no_grad():
    for data in calibration_loader:
        model_prepared(data)

# 轉換為真正的 INT8 模型
model_int8 = convert(model_prepared)

# 驗證:模型大小縮小 ~4×
print(f"FP32 大小: {get_size_mb(model):.1f} MB")
print(f"INT8 大小: {get_size_mb(model_int8):.1f} MB")

量化陷阱:不是所有層都適合量化

LayerNorm、Softmax、Positional Encoding 等包含除法與指數運算的層,量化後精度損失會很大。 實務上通常保留這些層為 FP32,只量化卷積層和線性層(即混合精度量化)。 TTS 模型更要注意:Vocoder 的後幾層對精度非常敏感,輕率量化會導致明顯的音質下降。

知識蒸餾(Knowledge Distillation)

知識蒸餾的概念很優雅:用一個大模型(Teacher)的輸出來指導一個小模型(Student)訓練, 讓 Student 學到 Teacher 「在各個類別之間的相對關係」,而不只是學硬標籤(0 或 1)。

Teacher-Student 訓練框架

Teacher Model

VITS 完整版(200MB)

已訓練好,凍結參數

輸出 Soft Labels

[0.7, 0.2, 0.08, 0.02]

(softmax with temperature T>1)

Student Model

精簡版(20MB)

學習 Teacher 的分布

Loss = α × Hard Loss + β × KL(Student ‖ Teacher)

為什麼 Soft Labels 比 Hard Labels 好?

Hard Label:貓 = [1, 0, 0, 0](告訴你「只有貓對」)
Soft Label:貓 = [0.70, 0.20, 0.08, 0.02](告訴你「最像貓,但也有點像狗」)

Soft Labels 包含 Teacher 學到的「類別間相似度」資訊, 讓 Student 在同樣的訓練數據量下學到更豐富的知識。

Knowledge Distillation Loss 實作
import torch.nn.functional as F

def distillation_loss(student_logits, teacher_logits, labels, T=4.0, alpha=0.5):
    """
    T: Temperature(溫度)—— 越高,Soft Labels 越平滑,包含更多「暗知識」
    alpha: Hard Loss 的權重,(1-alpha) 給 Soft Loss
    """
    # Hard Loss:Student 輸出 vs 真實標籤
    hard_loss = F.cross_entropy(student_logits, labels)

    # Soft Loss:Student 與 Teacher 的輸出分布 KL 散度
    soft_student = F.log_softmax(student_logits / T, dim=-1)
    soft_teacher = F.softmax(teacher_logits / T, dim=-1)
    soft_loss = F.kl_div(soft_student, soft_teacher, reduction='batchmean') * (T ** 2)

    return alpha * hard_loss + (1 - alpha) * soft_loss

TTS 中的蒸餾應用:Parallel WaveNet

Google 的 Parallel WaveNet 正是用知識蒸餾解決了 WaveNet 速度問題: 用慢而準的自回歸 WaveNet 作 Teacher,訓練一個可以並行生成的 IAF(Inverse Autoregressive Flow)Student。 結果:音質幾乎不變,但速度提升 1000×。這是蒸餾在工業界最知名的成功案例之一。

NPU/DSP 硬體加速

壓縮只是第一步,要真正跑得快,還需要讓模型運算落在正確的硬體單元上。 現代 SoC 通常包含多種運算核心,各有擅長的任務。

🧠

CPU

Central Processing Unit

擅長

通用控制邏輯,序列運算

不擅長

矩陣乘法效率差

🖥️

DSP

Digital Signal Processor

擅長

音訊處理、FFT、濾波器,極低功耗

不擅長

程式設計複雜,不適合大矩陣乘法

NPU

Neural Processing Unit

擅長

INT8/INT4 矩陣乘法,TFLite/ONNX Runtime 支援

不擅長

只支援固定格式,靈活性差

TTS 管線的硬體分工策略

CPU

文字前處理(Text Normalization)

規則型邏輯,不需要矩陣運算

NPU

聲學模型(FastSpeech2 / VITS Prior)

INT8 矩陣乘法,NPU 最高效

NPU / DSP

聲碼器(HiFi-GAN Generator)

卷積運算密集,NPU 優先;若 NPU 不支援則 DSP

DSP

音訊後處理(EQ、降噪)

DSP 原生支援 FIR/IIR 濾波

TFLite 模型轉換與 NPU 部署(Android 示例)
import tensorflow as tf

# 1. 將 PyTorch/ONNX 模型轉為 TFLite + INT8
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.int8]

# 2. 提供校準數據集(必須是真實數據,~100 筆即可)
def calibration_data_gen():
    for sample in calibration_dataset:
        yield [sample]

converter.representative_dataset = calibration_data_gen
tflite_model = converter.convert()

# 3. 儲存模型
with open('tts_model_int8.tflite', 'wb') as f:
    f.write(tflite_model)

# --- Android 端使用 NNAPI(自動路由到 NPU)---
# val options = Interpreter.Options().apply {
#     addDelegate(NnApiDelegate())  // 自動選擇 NPU/DSP/GPU
# }
# val interpreter = Interpreter(loadModel("tts_model_int8.tflite"), options)

實戰踩坑:邊緣部署常見問題

💾 OOM(Out of Memory):模型過大無法載入

  • 使用 mmap(記憶體映射)載入模型,避免一次性全部載入 RAM
  • 拆分模型成多個子圖(Graph Partition),分段推理
  • 對 Embedding Table 單獨量化(往往是最大的張量)

⏱️ 延遲過高:模型跑了但太慢

  • 使用 TFLite Profiler 或 ONNX Runtime profiling 找出瓶頸層
  • 檢查是否有 CPU Fallback(NPU 不支援的算子自動退回 CPU,速度大跌)
  • Reshape / Transpose 操作往往是隱藏殺手,嘗試調整模型佈局(NHWC vs NCHW)

🔊 音質下降:量化後聲音失真或有雜音

  • 對 Vocoder 最後幾層保留 FP16/FP32(混合精度量化)
  • 增加校準數據集的多樣性(不同說話者、語速、情緒)
  • 嘗試 QAT 而非 PTQ,讓模型學會補償量化誤差

🐛 推理結果不一致:PC 測試好但裝置上有問題

  • 確認 FP32 與 INT8 的數值範圍:edge case 的輸入(極短句、特殊字元)可能導致整數溢位
  • 確認輸入前處理(normalize、padding)與訓練時完全一致
  • 注意端序(Endianness)與資料對齊問題(某些 DSP 要求 8-byte aligned)

效能三角:品質、速度、功耗的取捨

邊緣 AI 部署沒有完美解,只有最適合你的應用場景的折衷方案。

音質
速度
功耗

三個頂點只能同時做好兩個

智慧音箱(接電源)

優先

音質 + 速度

犧牲

功耗(不重要)

策略

FP16 量化 + NPU,不需要激進壓縮

智慧手錶(電池)

優先

功耗 + 速度

犧牲

音質(可接受降級)

策略

INT4 量化 + 剪枝 50% + 較小的聲碼器

助聽器(極小電池)

優先

功耗

犧牲

音質 + 速度

策略

高度剪枝 + DSP 固定點運算 + 降採樣(16kHz→8kHz)

未來趨勢

🔍

神經架構搜索(NAS)

自動設計最適合目標硬體的模型架構,取代人工設計壓縮策略

🎯

硬體感知量化(HAQ)

根據不同層的敏感度與硬體支援,自動決定每層的量化位元數

🎙️

On-device 微調

在裝置上用極少數語音樣本快速個人化 TTS 音色,無需雲端

📱

統一框架(ONNX Runtime Mobile)

一次導出,跨 iOS/Android/MCU 自動適配最佳後端

本篇重點回顧

✂️剪枝(Pruning):移除冗餘權重。邊緣部署優先選結構化剪枝,因為非結構化剪枝需要特殊硬體才能加速。
🔢量化(Quantization):FP32 → INT8 縮小 4× 且加速 2–4×。PTQ 快速驗證,QAT 保精度;敏感層保留 FP32。
🎓知識蒸餾(KD):Teacher 的 Soft Labels 包含「暗知識」,讓 Student 在同等數據下學得更好。
NPU/DSP 分工:聲學模型跑 NPU(INT8 矩陣乘法),音訊後處理跑 DSP(原生 FIR/IIR),別讓 CPU 做它不擅長的事。
⚖️效能三角:品質、速度、功耗只能同時優化兩個。壓縮策略必須根據應用場景(手錶 vs 音箱 vs 助聽器)做出明確取捨。

至此,AI 離線 TTS 系列的技術核心已全部涵蓋:
EP.03 Dify 工作流 → EP.04 Transformer 基礎 → EP.05 TTS 模型演進 → EP.06 嵌入式落地。
EP.08 將把視野拉大,從 TTS 的 Encoder-Decoder 出發,延伸到 BERT、GPT、LLM 的整個語言模型演進史。

Model Compression
Pruning
Quantization
Knowledge Distillation
NPU
Edge AI
TFLite
EP.06