Skip to content

ONNX 最佳化 / 量化

原版 Style-Bert-VITS2 基於 PyTorch,僅用 CPU 難以即時推論。

HayaKoe 將 BERT 匯出為 Q8 量化 ONNX,Synthesizer 匯出為 FP32 ONNX 並在 ONNX Runtime 上執行,將 CPU 推論速度 按文本長度提升了 1.5x ~ 3.3x

同時將 1 說話人載入時的 RAM 使用量從 5,122 MB 降至 2,346 MB (-54%)

得益於同一路徑,不僅 x86_64,aarch64 (Raspberry Pi 等) 也能用相同程式碼執行

不足之處

原版 SBV2 (CPU, PyTorch FP32) 有兩個不足。

  • 速度 — 文本越長推論時間指數級增長。short (1.7 s 音訊) 倍速 1.52x,而 xlong (38.5 s 音訊) 推論 35.3 秒·倍速 1.09x,勉強跟上即時。
  • 記憶體 — 單說話人推論 Peak 記憶體約 5 GB 以上,負擔較重。

分析

先看模型參數分布,全部參數中約 84% 集中在 BERT (DeBERTa-v2-Large-Japanese, 約 329 M),Synthesizer (VITS) 為 63 M 約 16%。

BERT 佔模型大部分,因此量化 BERT 預計可以大幅減少記憶體。在 PyTorch 中僅對 BERT 套用 Q8 Dynamic Quantization (torch.quantization.quantize_dynamic) 進行驗證。

設定平均推論時間RAM
PyTorch BERT FP324.796 s+1,698 MB
PyTorch BERT Q84.536 s+368 MB (-78%)

確認 BERT 量化雖不改善速度,但能大幅減少記憶體使用。

在此基礎上透過 轉到 ONNX Runtime 進一步確保速度改善。

ONNX Runtime 在載入模型時自動套用 圖級最佳化

  • Kernel fusion — 將連續多個運算合併為一個。例如將 Conv → BatchNorm → Activation 三步合為一步,消除中間結果的記憶體寫入和再讀取,減少記憶體存取從而加速。
  • Constant folding — 將不論輸入如何始終產生相同值的運算在載入時預先計算好,推論時使用預計算值加速。
  • 刪除不必要節點 — 找出未使用、重複或執行無意義運算的節點並刪除。

總之,重構為數學上等價但推論更最佳化的運算。

Synthesizer 參數量僅 63 M,量化的記憶體收益有限,且 Flow 層(rational_quadratic_spline)在 FP16 以下數值不穩定,因此排除在量化對象之外。僅匯出為 ONNX 以獲取圖最佳化收益。

BERT 最佳化

為確認量化是否影響音質,在同一文本·同一說話人下比較了 FP32 · Q8 · Q4 三種 BERT 設定的輸出(Synthesizer 均為 FP32 固定)。

旅の途中で不思議な街に辿り着きました。少し寄り道していきましょう。

(旅途中來到了一個神奇的小鎮。稍微繞道走走吧。)

つくよみちゃんBERT dtype = FP32原版 baseline
0:00 / 0:00

FP32 和 Q8 在直接聽辨時難以一致地區分。

Q4 在大部分區間與 FP32 · Q8 相似,但在尾部可以聽到微小差異。

設定BERT 大小RAM (1 說話人)
FP321,157 MB1,599 MB
Q8497 MB1,079 MB (-33%)
Q4394 MB958 MB (-40%)

判斷進一步 Q4 量化獲得的記憶體收益不足以彌補音質差異,決定 預設使用 Q8

Synthesizer 最佳化

BERT 佔參數的 84%,按理快速化 BERT 就能加速整體。

但實際分別測量 BERT 和 Synthesizer 的推論時間後發現,CPU 時間的大部分消耗在 Synthesizer 側

PyTorch FP32 CPU 下的實測結果(5 次平均)。

文本BERTSynthesizerBERT 佔比Synth 佔比
short (1.7 s)0.489 s0.885 s36%64%
medium (5.3 s)0.602 s2.504 s19%81%
long (7.8 s)0.690 s3.714 s16%84%
xlong (30 s)1.074 s11.410 s9%91%

文本越長 Synthesizer 佔比越大,因為 BERT 對文本長度相對不敏感,而 Synthesizer 時間與要生成的音訊長度成正比增長。

實際僅量化 BERT Q8 時整體推論時間僅減少約 5%。

也就是說,要提速必須最佳化 Synthesizer 部分

Synthesizer 採用 僅 ONNX 轉換 而非量化。

  • VITS 的 Flow 層(rational_quadratic_spline)在 FP16 以下因浮點誤差導致 assertion error,無法量化。
  • 參數量僅 63 M,量化的記憶體收益也有限。

透過轉換到 ONNX Runtime,將前述圖級最佳化(kernel fusion · constant folding · 刪除不必要節點)同樣套用到 Synthesizer。

ONNX Runtime + CPUExecutionProvider

BERT 量化和 Synthesizer 圖最佳化都在 ONNX Runtime 上執行。

此外透過 intra-op parallelism 將單個運算分散到多個 CPU 核心,即使只有一個請求也能利用全部 CPU。

改善效果

CPU 效能對比(倍速,同一硬體)

倍速 = 音訊時長 / 推論時間(值越大越快)。

設定short (1.7 s)medium (7.6 s)long (10.7 s)xlong (38.5 s)
SBV2 PyTorch FP321.52x2.27x2.16x1.09x
SBV2 ONNX FP321.76x3.09x3.26x2.75x
HayaKoe (Q8 BERT + FP32 ONNX)2.50x3.35x3.33x3.60x

相比 PyTorch FP32 的速度提升為 按文本長度 1.5x ~ 3.3x

記憶體(1 說話人載入基準)

設定RAM
SBV2 PyTorch FP325,122 MB
SBV2 ONNX FP322,967 MB
HayaKoe (Q8 BERT + FP32 ONNX)2,346 MB (-54%)