File size: 15,542 Bytes
4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 9714659 4e9e0e4 9714659 4e9e0e4 6fbb52c 9714659 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c aff2f95 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c 4e9e0e4 6fbb52c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | # BlueMagpie-TTS
<p align="center">
<img src="assets/icon.png" alt="OpenFormosa Blue Magpie TTS" width="260">
</p>
BlueMagpie-TTS是一套文字轉語音(TTS)模型,能把文字合成為自然的語音。它支援三種使用情境:
- **一般語音合成** —— 直接把文字唸出來。
- **聲音複製** —— 提供一段參考音檔,讓輸出模仿該語者的音色。
- **指定語者** —— 用事先準備好的語者向量來控制音色。
同時也提供**串流輸出**,適合需要邊合成邊播放的應用。
🔊 **線上試玩**:[BlueMagpie-TTS Demo(Hugging Face Space)](https://huggingface.co/spaces/voidful/BlueMagpie-TTS-Demo)
## 命名由來
專案全名是 **OpenFormosa Blue Magpie TTS**。「藍鵲」取自**台灣藍鵲**(Taiwan Blue Magpie,學名 *Urocissa caerulea*)。選牠作為 TTS 的識別有幾層用意:
- **會發聲、辨識度高**:台灣藍鵲本來就是叫聲響亮、容易辨認的鳥,恰好呼應 TTS「把文字變成聲音」的核心。
- **長尾的動態感**:標誌性的長尾巴帶來流動、延展的視覺意象,比一般的喇叭 icon 更有記憶點與品牌個性。
- **立足台灣**:OpenFormosa(福爾摩沙)點出專案面向台灣華語、在地化語音的定位。
## 安裝
先把專案 clone 下來,再以可編輯模式安裝:
```bash
git clone https://github.com/OpenFormosa/BlueMagpie-TTS
cd BlueMagpie-TTS
pip install -e .
```
安裝過程會自動從 GitHub 取得相依的 [`barbet`](https://github.com/OpenFormosa/Barbet) 套件(負責文字語意的語言模型)。語音合成所需的聲學模組已內含於專案中(位於 `bluemagpie/_vendor/`,原始碼取自 [VoxCPM](https://github.com/OpenBMB/VoxCPM),採 Apache-2.0 授權),不需另外安裝。
若要儲存合成出來的音檔,建議另外安裝 `soundfile`:
```bash
pip install soundfile
```
## 載入模型
### 從 Hugging Face 下載
```python
import os
from huggingface_hub import snapshot_download
from transformers import PreTrainedTokenizerFast
from bluemagpie import BlueMagpieModel
model_dir = snapshot_download("OpenFormosa/BlueMagpie-TTS", token=True)
# 直接從 tokenizer.json 載入 tokenizer,相容較新版 transformers(5.x)
tokenizer = PreTrainedTokenizerFast(tokenizer_file=os.path.join(model_dir, "tokenizer.json"))
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, training=False, device="cuda")
```
### 從本機目錄載入
如果你已經有一份模型檔案,直接指向該目錄即可:
```python
import os
from transformers import PreTrainedTokenizerFast
from bluemagpie import BlueMagpieModel
model_dir = "checkpoints/bluemagpie"
tokenizer = PreTrainedTokenizerFast(tokenizer_file=os.path.join(model_dir, "tokenizer.json"))
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, training=False, device="cuda")
```
- `device` 可填 `"cuda"` 或 `"cpu"`,不指定時會自動選擇。
- 推論時請固定使用 `training=False`。
## 基本使用:文字轉語音
`generate` 會回傳一段語音波形(`torch.Tensor`),搭配 `soundfile` 即可存成 `.wav`。輸出的取樣率可由 `model.sample_rate` 取得:
```python
import soundfile as sf
audio = model.generate(
target_text="今天天氣真好。",
cfg_value=2.0,
)
sf.write("output.wav", audio.squeeze().cpu().numpy(), model.sample_rate)
```
## 聲音複製:模仿參考語者的音色
有兩種做法。
**A. 語者向量(`speaker_centroid`)** —— 從參考音檔抽出語者向量再合成(免逐字稿):
```bash
pip install -e ".[clone]" # 抽取需要 speechbrain(ECAPA-TDNN)
python scripts/extract_speaker_centroid.py --audio reference.wav --out my_voice.pt
# 多段同一語者更穩定:--audio a.wav b.wav c.wav
```
```python
import torch
centroid = torch.load("my_voice.pt", weights_only=True) # [192] 語者向量
audio = model.generate(
target_text="今天天氣真好。",
speaker_centroid=centroid,
cfg_value=2.8,
)
# 也可以在程式中直接抽取(不寫檔):
from bluemagpie import extract_speaker_centroid
centroid = extract_speaker_centroid("reference.wav") # [192]
```
**B. 參考音檔(`reference_wav_path`)** —— 直接提供一段參考音檔:
```python
audio = model.generate(
target_text="今天天氣真好。",
reference_wav_path="reference.wav",
cfg_value=2.8,
)
```
## 指定語者:以語者向量控制音色
模型自帶一個**多語者向量表** `checkpoints/speaker_centroids.pt`,目前內含兩個語者:
| 語者 ID | 說明 | 建議 `cfg_value` |
| --- | --- | --- |
| `hung_yi_lee` | 李宏毅(Hung-yi Lee)老師的語者向量(已取得本人授權;官方最佳參數即針對此語者調校) | 2.0–2.8 |
| `female_voice` | 一個通用女聲語者向量 | 2.0–2.8 |
向量表的格式為 `{"speaker_ids": [...], "centroids": tensor[N, 192], "dim": 192}`。以 `torch.load` 載入後,**依語者 ID 取出該語者的 `[192]` 向量**,再透過 `speaker_centroid` 指定音色:
```python
import os
import torch
table = torch.load(
os.path.join(model_dir, "checkpoints", "speaker_centroids.pt"),
map_location="cpu",
weights_only=True,
)
print(table["speaker_ids"]) # ['hung_yi_lee', 'female_voice']
# 切換語者只要改這一行("hung_yi_lee" 或 "female_voice")
speaker_id = "female_voice"
speaker_centroid = table["centroids"][table["speaker_ids"].index(speaker_id)] # [192]
audio = model.generate(
target_text="今天天氣真好。",
speaker_centroid=speaker_centroid, # 也可以直接傳入你自己已取得授權的語者向量
cfg_value=2.0,
)
```
只用模型 ID(尚未先 `snapshot_download` 整個模型)時,可單獨抓向量表這一個檔:
```python
from huggingface_hub import hf_hub_download
path = hf_hub_download("OpenFormosa/BlueMagpie-TTS", "checkpoints/speaker_centroids.pt")
table = torch.load(path, map_location="cpu", weights_only=True)
```
> 想新增更多語者,用上方〈聲音複製〉的 `extract_speaker_centroid` 抽出你自己(已取得授權)的 `[192]` 向量即可,傳法完全相同。早期僅含李宏毅單一語者的 `checkpoints/hung_yi_lee_speaker_centroids.pt`(格式相同)仍保留可用。
## 串流輸出
需要邊合成邊播放時,改用 `generate_streaming`。它是一個產生器,會一段一段地回傳音訊區塊:
```python
chunks = []
for chunk in model.generate_streaming(target_text="今天天氣真好。"):
chunks.append(chunk)
# 這裡可以即時播放或寫出 chunk
```
> 注意:串流模式下不支援自動重試(`retry_badcase`)。
## 四種輸入模式
模型支援四種輸入組合,皆透過同一個 `generate` 介面切換:
| 模式 | 需要的參數 | 用途 |
|---|---|---|
| 一般合成 | `target_text` | 直接把文字唸出來 |
| 語音接續 | `target_text`、`prompt_text`、`prompt_wav_path` | 從一段已有的語音與其文字接著往下唸 |
| 參考音檔 | `target_text`、`reference_wav_path` | 模仿參考音檔的語者音色 |
| 語者向量 | `target_text`、`speaker_centroid` | 以語者向量複製音色 |
## `generate` 常用參數
| 參數 | 預設值 | 說明 |
|---|---|---|
| `target_text` | (必填) | 要合成的文字 |
| `prompt_text` | `""` | 提示文字,搭配 `prompt_wav_path` 做語音接續 |
| `prompt_wav_path` | `""` | 提示音檔路徑,用於語音接續 |
| `reference_wav_path` | `""` | 參考音檔路徑,用於聲音複製 |
| `speaker_centroid` | `None` | 語者向量,用於指定音色 |
| `cfg_value` | `2.0` | 引導強度,數值越大越貼合條件、但可能較不自然 |
| `inference_timesteps` | `10` | 取樣步數,越多通常品質越好、速度越慢 |
| `min_len` / `max_len` | `2` / `2000` | 輸出長度的下限與上限 |
| `retry_badcase` | `False` | 偵測到異常輸出時自動重試(串流模式不支援) |
## 批次推論引擎(多請求加速)
若你要同時處理多筆合成請求、追求更高吞吐量,可改用內建的批次推論引擎 `BlueMagpieEngine`。它採用**連續批次**(continuous batching):多筆請求會一起批次解碼,新請求能在解碼途中加入,彼此互不影響。
引擎的特點:
- **不需額外相依套件**:只用到 `torch`,不必安裝 vLLM、flash-attn 等套件。
- **跨裝置**:CUDA、Apple Silicon(MPS)、CPU 共用同一套程式碼;CUDA 專屬的最佳化會自動偵測並啟用,其餘裝置自動略過。
- **與單筆 `generate` 數值一致**:在 batch=1 時,輸出與 `model.generate` 逐值相同(`model.generate` 始終是對照基準)。
### 基本用法
```python
import soundfile as sf
from bluemagpie.serving import BlueMagpieEngine, EngineConfig, Request
# model 與 tokenizer 的載入方式同前(from_local)
engine = BlueMagpieEngine(model, EngineConfig(max_num_seqs=16))
engine.add_request(Request(target_text="今天天氣真好。", seed=0))
engine.add_request(Request(target_text="第二句話。", reference_wav_path="speaker.wav"))
for out in engine.run(): # 依請求加入順序回傳
# out.audio:48 kHz 波形(已掛載 AudioVAE 時);out.latents:潛在表徵 [T, p, d]
sf.write(f"output_{out.request_id}.wav", out.audio.numpy(), out.sample_rate)
```
`Request` 支援與 `generate` 相同的四種輸入模式(一般合成、語音接續、參考音檔、語者向量),欄位對應 `target_text`、`prompt_text`、`prompt_wav_path`、`reference_wav_path`、`speaker_centroid`、`cfg_value`、`inference_timesteps` 等。每筆請求可給定 `seed`,使該請求的輸出與同批其他請求的數量、加入順序無關。
### 串流輸出
`engine.stream()` 是一個產生器,會在每一步逐筆回傳各請求的區塊:
```python
for chunk in engine.stream():
# chunk.request_id、chunk.latents、chunk.audio、chunk.finished
play_or_write(chunk)
```
> 一般合成、參考音檔、語者向量這三種模式會回傳串流音訊(`chunk.audio`);語音接續模式目前只回傳 `latents`,需要音訊時請改用 `run()`。
### 設定
`EngineConfig` 常用參數:
| 參數 | 預設值 | 說明 |
|---|---|---|
| `max_num_seqs` | `16` | 同時批次處理的最大請求數 |
| `max_model_len` | `2048` | 每筆序列的最大長度(提示+生成) |
| `inference_timesteps` | `9` | 取樣步數 |
| `cfg_value` | `2.8` | 引導強度 |
| `enforce_eager` | `True` | 維持與單筆 `generate` 數值一致的路徑 |
| `compile` | `False` | 啟用 `torch.compile`(僅 CUDA 有效,其餘裝置自動略過) |
> 引擎的設計、取捨與已知限制詳見 [`src/bluemagpie/serving/DESIGN.md`](src/bluemagpie/serving/DESIGN.md)。
### 加速原理:為什麼不是直接套 vLLM?
很多人期待「用 vLLM 之類的框架就能加速」,但對 BlueMagpie 來說,直接套 vLLM 並不可行,原因有二:
1. **真正的運算瓶頸不在語言模型,而在擴散式解碼器。** 每生成一個音訊單元,DiT(擴散式解碼器 LocDiT/CFM)要被呼叫約 16–18 次(取樣步數 × 無條件/有條件兩路),而語言模型(Barbet、RALM)各只跑一次。vLLM 是**文字語言模型**的推論框架,它根本不處理擴散式解碼器——就算把語言模型搬到 vLLM,主要運算量仍是 eager 執行,端到端不會明顯變快。
2. **vLLM 不支援 Barbet 的混合架構。** BlueMagpie 的語意語言模型 Barbet 是 Mamba2 與注意力的混合模型,vLLM(以及 nano-vllm、vllm-omni)對這種混合 TSLM 是零支援,得自行實作一個 first-class 混合模型才跑得起來,工程量大且僅限 CUDA。
因此本引擎改採**借用 vLLM 的架構技術、但不依賴它的 CUDA 套件**的做法:
- **連續批次**處理多請求(吞吐量的主要來源),跨請求共用批次運算。
- 以 **padded KV cache + SDPA + 遮罩**取代 vLLM 的 PagedAttention/FlashAttention,換取跨裝置、零依賴(代價是單一運算略慢、記憶體較不精省)。
- Barbet 的 Mamba 狀態以**純 PyTorch 單步遞迴**處理,不需融合 kernel。
- 可選的 `compile=True` 透過 `torch.compile`(內部即 CUDA graphs)加速 **DiT 與 LocEnc**——也就是真正的熱點,而這正是直接套 vLLM 不會幫你做的部分。
> 一句話總結:我們不追求單一運算比 vLLM 快,而是用 vLLM 級的**批次調度**搭配**針對 DiT 瓶頸的最佳化**,在零額外依賴、跨裝置的前提下提升整體吞吐量。
## Apple Silicon MLX 加速(選用)
在 Apple Silicon(M 系列)上,可改用 **MLX** 原生路徑,直接在 Apple GPU(Metal、統一記憶體)上推論,通常比 PyTorch 的 MPS 後端更快。這是**選用**功能,核心仍維持 torch-only:
```bash
pip install -e .[mlx]
```
```python
from bluemagpie import BlueMagpieModel
from bluemagpie.mlx import BlueMagpieMLX, mlx_generate
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, device="cpu")
mlx_model = BlueMagpieMLX(model) # 轉換權重(只需一次)
import soundfile as sf
audio = mlx_generate(model, mlx_model, "今天天氣真好。", seed=0) # 48 kHz 波形
sf.write("output.wav", audio.numpy(), model.sample_rate)
```
- 整條推論路徑(Barbet、RALM、LocEnc、LocDiT/CFM、**AudioVAE 解碼器**、AR 迴圈)皆以 MLX 重寫,並逐模組對 PyTorch 做數值 parity 驗證——生成過程可完全不經 PyTorch(僅斷詞與參考音檔編碼仍用 torch)。
- decode 採用快取式單步(cached step),逐步推進、不重算整個序列。
- `mlx_generate` 支援與 `generate` 相同的四種輸入模式(一般合成、語音接續、參考音檔、語者向量)。
- 在**真實 7.75GB 模型**上端到端 **RTF 0.77**(比即時還快)——比 torch-MPS 快約 **1.45×**、比 torch-CPU 快約 **3.27×**(fp32,`scripts/bench_rtf.py`)。設計與限制詳見 [`src/bluemagpie/mlx/DESIGN.md`](src/bluemagpie/mlx/DESIGN.md)。
## 注意事項
- 上面範例都直接從 `tokenizer.json` 載入 tokenizer 再傳給 `from_local`,在較新版 transformers(5.x)也能穩定運作;背後原因見〈疑難排解〉。
- 沒有 GPU 也可以執行:把 `device` 設為 `"cpu"` 即可(速度較慢,但短句合成只需數十秒)。輸出為 48 kHz 單聲道。
- 模型內附的 `hung_yi_lee` 語者向量已取得本人授權,可直接作為範例使用;指定其他語者或進行聲音複製時,請只使用你已取得授權的參考音檔或語者向量。
- 請妥善保管語者向量表與合成出來的音檔,未經授權前不要對外散布。
## 疑難排解
**較新版 transformers(5.x)的 tokenizer 載入**
上面的範例都直接從 `tokenizer.json` 載入 tokenizer 再傳給 `from_local`,因此在 transformers 5.x 也能正常運作,不需額外處理(模型只用到 tokenizer 的 `encode`)。
若你改用 `from_local` 的自動載入(不傳入 `tokenizer`),在 transformers 5.x 可能會失敗 —— 解析 `tokenizer_config.json` 時出現
```
TypeError: ..._patch_mistral_regex() got multiple values for keyword argument 'fix_mistral_regex'
```
或載入看似成功、但呼叫 `generate()` 時才報 `ValueError: No tokenizer attached to BlueMagpieModel`。遇到時改回上面範例的明確載入方式即可。
|