BlueMagpie-TTS / README.md
voidful's picture
Docs: document bundled multi-speaker centroid table (hung_yi_lee + female_voice)
6fbb52c verified
|
Raw
History Blame Contribute Delete
15.5 kB
# 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`。遇到時改回上面範例的明確載入方式即可。