Update BlueMagpie-TTS checkpoint step_0006000
Browse filesUpload inference-only BlueMagpie-TTS step_0006000 checkpoint.
Includes model weights, AudioVAE weights, tokenizer files, sanitized config, model card, usage guide, and release metadata.
Deletes remote speaker-centroid artifacts under checkpoints/ so no experimental speaker tables are shipped.
- .gitattributes +0 -3
- README.md +84 -279
- USAGE.md +55 -321
- checkpoints/hung_yi_lee_speaker_centroids.pt +0 -3
- checkpoints/speaker_centroids.pt +0 -0
- config.json +2 -27
- pytorch_model.bin +1 -1
- release_metadata.json +25 -33
.gitattributes
CHANGED
|
@@ -1,5 +1,2 @@
|
|
| 1 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
checkpoints/hung_yi_lee_speaker_centroids.pt filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
assets/icon.png filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
assets/social-preview.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,325 +1,130 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
| 12 |
|
| 13 |
-
|
| 14 |
|
| 15 |
-
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
- **會發聲、辨識度高**:台灣藍鵲本來就是叫聲響亮、容易辨認的鳥,恰好呼應 TTS「把文字變成聲音」的核心。
|
| 21 |
-
- **長尾的動態感**:標誌性的長尾巴帶來流動、延展的視覺意象,比一般的喇叭 icon 更有記憶點與品牌個性。
|
| 22 |
-
- **立足台灣**:OpenFormosa(福爾摩沙)點出專案面向台灣華語、在地化語音的定位。
|
| 23 |
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
|
| 27 |
|
| 28 |
```bash
|
| 29 |
-
git clone https://github.com/
|
| 30 |
cd BlueMagpie-TTS
|
| 31 |
-
pip install -e .
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
安裝過程會自動從 GitHub 取得相依的 [`barbet`](https://github.com/OpenFormosa/Barbet) 套件(負責文字語意的語言模型)。語音合成所需的聲學模組已內含於專案中(位於 `bluemagpie/_vendor/`,原始碼取自 [VoxCPM](https://github.com/OpenBMB/VoxCPM),採 Apache-2.0 授權),不需另外安裝。
|
| 35 |
-
|
| 36 |
-
若要儲存合成出來的音檔,建議另外安裝 `soundfile`:
|
| 37 |
-
|
| 38 |
-
```bash
|
| 39 |
-
pip install soundfile
|
| 40 |
```
|
| 41 |
|
| 42 |
-
##
|
| 43 |
-
|
| 44 |
-
### 從 Hugging Face 下載
|
| 45 |
|
| 46 |
```python
|
| 47 |
-
import os
|
| 48 |
from huggingface_hub import snapshot_download
|
| 49 |
-
|
| 50 |
-
from bluemagpie import BlueMagpieModel
|
| 51 |
-
|
| 52 |
-
model_dir = snapshot_download("OpenFormosa/BlueMagpie-TTS", token=True)
|
| 53 |
-
# 直接從 tokenizer.json 載入 tokenizer,相容較新版 transformers(5.x)
|
| 54 |
-
tokenizer = PreTrainedTokenizerFast(tokenizer_file=os.path.join(model_dir, "tokenizer.json"))
|
| 55 |
-
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, training=False, device="cuda")
|
| 56 |
-
```
|
| 57 |
-
|
| 58 |
-
### 從本機目錄載入
|
| 59 |
-
|
| 60 |
-
如果你已經有一份模型檔案,直接指向該目錄即可:
|
| 61 |
|
| 62 |
-
```python
|
| 63 |
-
import os
|
| 64 |
-
from transformers import PreTrainedTokenizerFast
|
| 65 |
from bluemagpie import BlueMagpieModel
|
| 66 |
|
| 67 |
-
model_dir = "
|
| 68 |
-
|
| 69 |
-
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, training=False, device="cuda")
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
- `device` 可填 `"cuda"` 或 `"cpu"`,不指定時會自動選擇。
|
| 73 |
-
- 推論時請固定使用 `training=False`。
|
| 74 |
-
|
| 75 |
-
## 基本使用:文字轉語音
|
| 76 |
-
|
| 77 |
-
`generate` 會回傳一段語音波形(`torch.Tensor`),搭配 `soundfile` 即可存成 `.wav`。輸出的取樣率可由 `model.sample_rate` 取得:
|
| 78 |
-
|
| 79 |
-
```python
|
| 80 |
-
import soundfile as sf
|
| 81 |
|
| 82 |
audio = model.generate(
|
| 83 |
-
target_text="
|
|
|
|
| 84 |
cfg_value=2.0,
|
|
|
|
|
|
|
| 85 |
)
|
| 86 |
|
| 87 |
-
sf.write("
|
| 88 |
```
|
| 89 |
|
| 90 |
-
##
|
| 91 |
-
|
| 92 |
-
有兩種做法。
|
| 93 |
-
|
| 94 |
-
**A. 語者向量(`speaker_centroid`)** —— 從參考音檔抽出語者向量再合成(免逐字稿):
|
| 95 |
-
|
| 96 |
-
```bash
|
| 97 |
-
pip install -e ".[clone]" # 抽取需要 speechbrain(ECAPA-TDNN)
|
| 98 |
-
python scripts/extract_speaker_centroid.py --audio reference.wav --out my_voice.pt
|
| 99 |
-
# 多段同一語者更穩定:--audio a.wav b.wav c.wav
|
| 100 |
-
```
|
| 101 |
-
|
| 102 |
-
```python
|
| 103 |
-
import torch
|
| 104 |
-
|
| 105 |
-
centroid = torch.load("my_voice.pt", weights_only=True) # [192] 語者向量
|
| 106 |
-
audio = model.generate(
|
| 107 |
-
target_text="今天天氣真好。",
|
| 108 |
-
speaker_centroid=centroid,
|
| 109 |
-
cfg_value=2.8,
|
| 110 |
-
)
|
| 111 |
-
|
| 112 |
-
# 也可以在程式中直接抽取(不寫檔):
|
| 113 |
-
from bluemagpie import extract_speaker_centroid
|
| 114 |
-
centroid = extract_speaker_centroid("reference.wav") # [192]
|
| 115 |
-
```
|
| 116 |
-
|
| 117 |
-
**B. 參考音檔(`reference_wav_path`)** —— 直接提供一段參考音檔:
|
| 118 |
|
| 119 |
```python
|
|
|
|
| 120 |
audio = model.generate(
|
| 121 |
-
target_text="今天
|
| 122 |
-
|
| 123 |
-
cfg_value=2.
|
| 124 |
-
|
| 125 |
-
```
|
| 126 |
-
|
| 127 |
-
## 指定語者:以語者向量控制音色
|
| 128 |
-
|
| 129 |
-
模型自帶一個**多語者向量表** `checkpoints/speaker_centroids.pt`,目前內含兩個語者:
|
| 130 |
-
|
| 131 |
-
| 語者 ID | 說明 | 建議 `cfg_value` |
|
| 132 |
-
| --- | --- | --- |
|
| 133 |
-
| `hung_yi_lee` | 李宏毅(Hung-yi Lee)老師的語者向量(已取得本人授權;官方最佳參數即針對此語者調校) | 2.0–2.8 |
|
| 134 |
-
| `female_voice` | 一個通用女聲語者向量 | 2.0–2.8 |
|
| 135 |
-
|
| 136 |
-
向量表的格式為 `{"speaker_ids": [...], "centroids": tensor[N, 192], "dim": 192}`。以 `torch.load` 載入後,**依語者 ID 取出該語者的 `[192]` 向量**,再透過 `speaker_centroid` 指定音色:
|
| 137 |
-
|
| 138 |
-
```python
|
| 139 |
-
import os
|
| 140 |
-
import torch
|
| 141 |
-
|
| 142 |
-
table = torch.load(
|
| 143 |
-
os.path.join(model_dir, "checkpoints", "speaker_centroids.pt"),
|
| 144 |
-
map_location="cpu",
|
| 145 |
-
weights_only=True,
|
| 146 |
)
|
| 147 |
-
print(table["speaker_ids"]) # ['hung_yi_lee', 'female_voice']
|
| 148 |
-
|
| 149 |
-
# 切換語者只要改這一行("hung_yi_lee" 或 "female_voice")
|
| 150 |
-
speaker_id = "female_voice"
|
| 151 |
-
speaker_centroid = table["centroids"][table["speaker_ids"].index(speaker_id)] # [192]
|
| 152 |
|
|
|
|
| 153 |
audio = model.generate(
|
| 154 |
-
target_text="今天
|
| 155 |
-
|
| 156 |
cfg_value=2.0,
|
|
|
|
| 157 |
)
|
| 158 |
-
```
|
| 159 |
-
|
| 160 |
-
只用模型 ID(尚未先 `snapshot_download` 整個模型)時,可單獨抓向量表這一個檔:
|
| 161 |
|
| 162 |
-
```python
|
| 163 |
-
from huggingface_hub import hf_hub_download
|
| 164 |
-
|
| 165 |
-
path = hf_hub_download("OpenFormosa/BlueMagpie-TTS", "checkpoints/speaker_centroids.pt")
|
| 166 |
-
table = torch.load(path, map_location="cpu", weights_only=True)
|
| 167 |
-
```
|
| 168 |
-
|
| 169 |
-
> 想新增更多語者,用上方〈聲音複製〉的 `extract_speaker_centroid` 抽出你自己(已取得授權)的 `[192]` 向量即可,傳法完全相同。早期僅含李宏毅單一語者的 `checkpoints/hung_yi_lee_speaker_centroids.pt`(格式相同)仍保留可用。
|
| 170 |
-
|
| 171 |
-
## 串流輸出
|
| 172 |
-
|
| 173 |
-
需要邊合成邊播放時,改用 `generate_streaming`。它是一個產生器,會一段一段地回傳音訊區塊:
|
| 174 |
-
|
| 175 |
-
```python
|
| 176 |
-
chunks = []
|
| 177 |
-
for chunk in model.generate_streaming(target_text="今天天氣真好。"):
|
| 178 |
-
chunks.append(chunk)
|
| 179 |
-
# 這裡可以即時播放或寫出 chunk
|
| 180 |
```
|
| 181 |
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
模型支援四種輸入組合,皆透過同一個 `generate` 介面切換:
|
| 187 |
-
|
| 188 |
-
| 模式 | 需要的參數 | 用途 |
|
| 189 |
-
|---|---|---|
|
| 190 |
-
| 一般合成 | `target_text` | 直接把文字唸出來 |
|
| 191 |
-
| 語音接續 | `target_text`、`prompt_text`、`prompt_wav_path` | 從一段已有的語音與其文字接著往下唸 |
|
| 192 |
-
| 參考音檔 | `target_text`、`reference_wav_path` | 模仿參考音檔的語者音色 |
|
| 193 |
-
| 語者向量 | `target_text`、`speaker_centroid` | 以語者向量複製音色 |
|
| 194 |
-
|
| 195 |
-
## `generate` 常用參數
|
| 196 |
-
|
| 197 |
-
| 參數 | 預設值 | 說明 |
|
| 198 |
-
|---|---|---|
|
| 199 |
-
| `target_text` | (必填) | 要合成的文字 |
|
| 200 |
-
| `prompt_text` | `""` | 提示文字,搭配 `prompt_wav_path` 做語音接續 |
|
| 201 |
-
| `prompt_wav_path` | `""` | 提示音檔路徑,用於語音接續 |
|
| 202 |
-
| `reference_wav_path` | `""` | 參考音檔路徑,用於聲音複製 |
|
| 203 |
-
| `speaker_centroid` | `None` | 語者向量,用於指定音色 |
|
| 204 |
-
| `cfg_value` | `2.0` | 引導強度,數值越大越貼合條件、但可能較不自然 |
|
| 205 |
-
| `inference_timesteps` | `10` | 取樣步數,越多通常品質越好、速度越慢 |
|
| 206 |
-
| `min_len` / `max_len` | `2` / `2000` | 輸出長度的下限與上限 |
|
| 207 |
-
| `retry_badcase` | `False` | 偵測到異常輸出時自動重試(串流模式不支援) |
|
| 208 |
-
|
| 209 |
-
## 批次推論引擎(多請求加速)
|
| 210 |
-
|
| 211 |
-
若你要同時處理多筆合成請求、追求更高吞吐量,可改用內建的批次推論引擎 `BlueMagpieEngine`。它採用**連續批次**(continuous batching):多筆請求會一起批次解碼,新請求能在解碼途中加入,彼此互不影響。
|
| 212 |
|
| 213 |
-
|
| 214 |
|
| 215 |
-
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
|
| 219 |
-
##
|
| 220 |
-
|
| 221 |
-
```python
|
| 222 |
-
import soundfile as sf
|
| 223 |
-
from bluemagpie.serving import BlueMagpieEngine, EngineConfig, Request
|
| 224 |
-
|
| 225 |
-
# model 與 tokenizer 的載入方式同前(from_local)
|
| 226 |
-
engine = BlueMagpieEngine(model, EngineConfig(max_num_seqs=16))
|
| 227 |
-
|
| 228 |
-
engine.add_request(Request(target_text="今天天氣真好。", seed=0))
|
| 229 |
-
engine.add_request(Request(target_text="第二句話。", reference_wav_path="speaker.wav"))
|
| 230 |
-
|
| 231 |
-
for out in engine.run(): # 依請求加入順序回傳
|
| 232 |
-
# out.audio:48 kHz 波形(已掛載 AudioVAE 時);out.latents:潛在表徵 [T, p, d]
|
| 233 |
-
sf.write(f"output_{out.request_id}.wav", out.audio.numpy(), out.sample_rate)
|
| 234 |
-
```
|
| 235 |
-
|
| 236 |
-
`Request` 支援與 `generate` 相同的四種輸入模式(一般合成、語音接續、參考音檔、語者向量),欄位對應 `target_text`、`prompt_text`、`prompt_wav_path`、`reference_wav_path`、`speaker_centroid`、`cfg_value`、`inference_timesteps` 等。每筆請求可給定 `seed`,使該請求的輸出與同批其他請求的數量、加入順序無關。
|
| 237 |
-
|
| 238 |
-
### 串流輸出
|
| 239 |
-
|
| 240 |
-
`engine.stream()` 是一個產生器,會在每一步逐筆回傳各請求的區塊:
|
| 241 |
-
|
| 242 |
-
```python
|
| 243 |
-
for chunk in engine.stream():
|
| 244 |
-
# chunk.request_id、chunk.latents、chunk.audio、chunk.finished
|
| 245 |
-
play_or_write(chunk)
|
| 246 |
-
```
|
| 247 |
|
| 248 |
-
|
|
|
|
| 249 |
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
|
| 252 |
-
|
| 253 |
|
| 254 |
-
|
|
| 255 |
-
|---|---|
|
| 256 |
-
|
|
| 257 |
-
|
|
| 258 |
-
|
|
| 259 |
-
|
|
| 260 |
-
| `enforce_eager` | `True` | 維持與單筆 `generate` 數值一致的路徑 |
|
| 261 |
-
| `compile` | `False` | 啟用 `torch.compile`(僅 CUDA 有效,其餘裝置自動略過) |
|
| 262 |
|
| 263 |
-
|
| 264 |
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
1. **真正的運算瓶頸不在語言模型,而在擴散式解碼器。** 每生成一個音訊單元,DiT(擴散式解碼器 LocDiT/CFM)要被呼叫約 16–18 次(取樣步數 × 無條件/有條件兩路),而語言模型(Barbet、RALM)各只跑一次。vLLM 是**文字語言模型**的推論框架,它根本不處理擴散式解碼器——就算把語言模型搬到 vLLM,主要運算量仍是 eager 執行,端到端不會明顯變快。
|
| 270 |
-
2. **vLLM 不支援 Barbet 的混合架構。** BlueMagpie 的語意語言模型 Barbet 是 Mamba2 與注意力的混合模型,vLLM(以及 nano-vllm、vllm-omni)對這種混合 TSLM 是零支援,得自行實作一個 first-class 混合模型才跑得起來,工程量大且僅限 CUDA。
|
| 271 |
-
|
| 272 |
-
因此本引擎改採**借用 vLLM 的架構技術、但不依賴它的 CUDA 套件**的做法:
|
| 273 |
-
|
| 274 |
-
- **連續批次**處理多請求(吞吐量的主要來源),跨請求共用批次運算。
|
| 275 |
-
- 以 **padded KV cache + SDPA + 遮罩**取代 vLLM 的 PagedAttention/FlashAttention,換取跨裝置、零依賴(代價是單一運算略慢、記憶體較不精省)。
|
| 276 |
-
- Barbet 的 Mamba 狀態以**純 PyTorch 單步遞迴**處理,不需融合 kernel。
|
| 277 |
-
- 可選的 `compile=True` 透過 `torch.compile`(內部即 CUDA graphs)加速 **DiT 與 LocEnc**——也就是真正的熱點,而這正是直接套 vLLM 不會幫你做的部分。
|
| 278 |
-
|
| 279 |
-
> 一句話總結:我們不追求單一運算比 vLLM 快,而是用 vLLM 級的**批次調度**搭配**針對 DiT 瓶頸的最佳化**,在零額外依賴、跨裝置的前提下提升整體吞吐量。
|
| 280 |
-
|
| 281 |
-
## Apple Silicon MLX 加速(選用)
|
| 282 |
-
|
| 283 |
-
在 Apple Silicon(M 系列)上,可改用 **MLX** 原生路徑,直接在 Apple GPU(Metal、統一記憶體)上推論,通常比 PyTorch 的 MPS 後端更快。這是**選用**功能,核心仍維持 torch-only:
|
| 284 |
-
|
| 285 |
-
```bash
|
| 286 |
-
pip install -e .[mlx]
|
| 287 |
-
```
|
| 288 |
-
|
| 289 |
-
```python
|
| 290 |
-
from bluemagpie import BlueMagpieModel
|
| 291 |
-
from bluemagpie.mlx import BlueMagpieMLX, mlx_generate
|
| 292 |
-
|
| 293 |
-
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, device="cpu")
|
| 294 |
-
mlx_model = BlueMagpieMLX(model) # 轉換權重(只需一次)
|
| 295 |
-
|
| 296 |
-
import soundfile as sf
|
| 297 |
-
audio = mlx_generate(model, mlx_model, "今天天氣真好。", seed=0) # 48 kHz 波形
|
| 298 |
-
sf.write("output.wav", audio.numpy(), model.sample_rate)
|
| 299 |
-
```
|
| 300 |
-
|
| 301 |
-
- 整條推論路徑(Barbet、RALM、LocEnc、LocDiT/CFM、**AudioVAE 解碼器**、AR 迴圈)皆以 MLX 重寫,並逐模組對 PyTorch 做數值 parity 驗證——生成過程可完全不經 PyTorch(僅斷詞與參考音檔編碼仍用 torch)。
|
| 302 |
-
- decode 採用快取式單步(cached step),逐步推進、不重算整個序列。
|
| 303 |
-
- `mlx_generate` 支援與 `generate` 相同的四種輸入模式(一般合成、語音接續、參考音檔、語者向量)。
|
| 304 |
-
- 在**真實 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)。
|
| 305 |
-
|
| 306 |
-
## 注意事項
|
| 307 |
-
|
| 308 |
-
- 上面範例都直接從 `tokenizer.json` 載入 tokenizer 再傳給 `from_local`,在較新版 transformers(5.x)也能穩定運作;背後原因見〈疑難排解〉。
|
| 309 |
-
- 沒有 GPU 也可以執行:把 `device` 設為 `"cpu"` 即可(速度較慢,但短句合成只需數十秒)。輸出為 48 kHz 單聲道。
|
| 310 |
-
- 模型內附的 `hung_yi_lee` 語者向量已取得本人授權,可直接作為範例使用;指定其他語者或進行聲音複製時,請只使用你已取得授權的參考音檔或語者向量。
|
| 311 |
-
- 請妥善保管語者向量表與合成出來的音檔,未經授權前不要對外散布。
|
| 312 |
-
|
| 313 |
-
## 疑難排解
|
| 314 |
-
|
| 315 |
-
**較新版 transformers(5.x)的 tokenizer 載入**
|
| 316 |
-
|
| 317 |
-
上面的範例都直接從 `tokenizer.json` 載入 tokenizer 再傳給 `from_local`,因此在 transformers 5.x 也能正常運作,不需額外處理(模型只用到 tokenizer 的 `encode`)。
|
| 318 |
-
|
| 319 |
-
若你改用 `from_local` 的自動載入(不傳入 `tokenizer`),在 transformers 5.x 可能會失敗 —— 解析 `tokenizer_config.json` 時出現
|
| 320 |
-
|
| 321 |
-
```
|
| 322 |
-
TypeError: ..._patch_mistral_regex() got multiple values for keyword argument 'fix_mistral_regex'
|
| 323 |
-
```
|
| 324 |
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- zh
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- text-to-speech
|
| 7 |
+
- speech-synthesis
|
| 8 |
+
- code-switching
|
| 9 |
+
library_name: bluemagpie
|
| 10 |
+
pipeline_tag: text-to-speech
|
| 11 |
+
license: other
|
| 12 |
+
---
|
| 13 |
|
| 14 |
+
# BlueMagpie-TTS
|
| 15 |
|
| 16 |
+
BlueMagpie-TTS checkpoint for research and evaluation.
|
| 17 |
|
| 18 |
+
This repository contains the inference artifact only: model weights, AudioVAE
|
| 19 |
+
weights, tokenizer files, config, and usage documentation. It does not include
|
| 20 |
+
optimizer state, scheduler state, training logs, speaker tables, local configs,
|
| 21 |
+
or training-data metadata.
|
| 22 |
|
| 23 |
+
## Intended Use
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
+
- Mandarin and mixed Mandarin/English text-to-speech evaluation.
|
| 26 |
+
- Experiments with two mutually exclusive generation modes:
|
| 27 |
+
centroid-only and speaker-reference-audio-only.
|
| 28 |
+
- Do not redistribute the checkpoint or generated speech unless rights and
|
| 29 |
+
consent are cleared for the intended use.
|
| 30 |
|
| 31 |
+
## Install
|
| 32 |
|
| 33 |
```bash
|
| 34 |
+
git clone https://github.com/voidful/BlueMagpie-TTS
|
| 35 |
cd BlueMagpie-TTS
|
| 36 |
+
pip install -e ".[train]"
|
| 37 |
+
pip install soundfile huggingface_hub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
```
|
| 39 |
|
| 40 |
+
## Quick Start
|
|
|
|
|
|
|
| 41 |
|
| 42 |
```python
|
|
|
|
| 43 |
from huggingface_hub import snapshot_download
|
| 44 |
+
import soundfile as sf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
|
|
|
|
|
|
|
|
|
| 46 |
from bluemagpie import BlueMagpieModel
|
| 47 |
|
| 48 |
+
model_dir = snapshot_download("OpenFormosa/BlueMagpie-TTS", token=True)
|
| 49 |
+
model = BlueMagpieModel.from_local(model_dir, training=False, device="cuda")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
audio = model.generate(
|
| 52 |
+
target_text="這是 AI TTS code switching 測試。",
|
| 53 |
+
reference_wav_path="reference_speaker.wav",
|
| 54 |
cfg_value=2.0,
|
| 55 |
+
inference_timesteps=10,
|
| 56 |
+
retry_badcase=True,
|
| 57 |
)
|
| 58 |
|
| 59 |
+
sf.write("sample.wav", audio.detach().cpu().numpy(), model.sample_rate)
|
| 60 |
```
|
| 61 |
|
| 62 |
+
## Conditioning Modes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
```python
|
| 65 |
+
# centroid-only
|
| 66 |
audio = model.generate(
|
| 67 |
+
target_text="今天的會議改到下午三點。",
|
| 68 |
+
speaker_centroid=centroid_tensor,
|
| 69 |
+
cfg_value=2.0,
|
| 70 |
+
inference_timesteps=10,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
# speaker-reference-audio-only: no reference transcript required
|
| 74 |
audio = model.generate(
|
| 75 |
+
target_text="今天的會議改到下午三點。",
|
| 76 |
+
reference_wav_path="reference_speaker.wav",
|
| 77 |
cfg_value=2.0,
|
| 78 |
+
inference_timesteps=10,
|
| 79 |
)
|
|
|
|
|
|
|
|
|
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
```
|
| 82 |
|
| 83 |
+
Only use reference audio or speaker centroids from speakers you have permission
|
| 84 |
+
to synthesize. Reference clips should be at least 3 seconds. The
|
| 85 |
+
speaker-reference-audio-only mode uses the raw reference-audio token path and
|
| 86 |
+
does not require a reference transcript.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
+
## Long Text
|
| 89 |
|
| 90 |
+
For long-form synthesis, split text into sentence-sized chunks and concatenate
|
| 91 |
+
the generated waveforms. For stronger continuity, keep the same centroid or
|
| 92 |
+
reference audio across chunks.
|
| 93 |
|
| 94 |
+
## Evaluation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
+
Numbers below are from an internal held-out evaluation set. The eval set and
|
| 97 |
+
training data are intentionally not described in this model card.
|
| 98 |
|
| 99 |
+
| System | Setting | CER | WER |
|
| 100 |
+
| --- | --- | ---: | ---: |
|
| 101 |
+
| BlueMagpie-TTS | centroid-only | 7.44% | 8.57% |
|
| 102 |
+
| BlueMagpie-TTS | speaker-reference-audio-only | 8.99% | 11.77% |
|
| 103 |
+
| Reference baseline | same internal eval | 11.45% | 14.83% |
|
| 104 |
|
| 105 |
+
Long-form diagnostics on the selected checkpoint:
|
| 106 |
|
| 107 |
+
| Metric | Value |
|
| 108 |
+
| --- | ---: |
|
| 109 |
+
| centroid-only CER / WER | 7.51% / 7.51% |
|
| 110 |
+
| speaker-reference-audio-only CER / WER | 8.92% / 8.92% |
|
| 111 |
+
| Median chars/sec | 4.017 |
|
| 112 |
+
| speaker-reference chunk speaker-sim drop | 0.109 |
|
|
|
|
|
|
|
| 113 |
|
| 114 |
+
## Limitations
|
| 115 |
|
| 116 |
+
- Metrics are not a public benchmark and should be used only for internal model
|
| 117 |
+
selection.
|
| 118 |
+
- Speaker similarity depends on the quality and rights-cleared status of the
|
| 119 |
+
supplied reference audio or centroid.
|
| 120 |
+
- Very long passages should be chunked to avoid stop-token and prosody drift.
|
| 121 |
+
- Generated speech may be incorrect; do not use it as a real-world notification
|
| 122 |
+
without human review.
|
| 123 |
|
| 124 |
+
## Files
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
- `pytorch_model.bin`: BlueMagpie model weights.
|
| 127 |
+
- `audiovae.pth`: AudioVAE weights.
|
| 128 |
+
- `config.json`: BlueMagpie architecture/runtime config.
|
| 129 |
+
- `tokenizer.json`, `tokenizer_config.json`: tokenizer files.
|
| 130 |
+
- `USAGE.md`: expanded usage guide.
|
USAGE.md
CHANGED
|
@@ -1,363 +1,97 @@
|
|
| 1 |
-
# BlueMagpie-TTS
|
| 2 |
|
| 3 |
-
|
| 4 |
-
from text. It supports three scenarios:
|
| 5 |
-
|
| 6 |
-
- **Plain synthesis** — read the text aloud.
|
| 7 |
-
- **Voice cloning** — mimic the timbre of a reference clip.
|
| 8 |
-
- **Speaker selection** — control the timbre with a prepared speaker vector.
|
| 9 |
-
|
| 10 |
-
It also supports **streaming output** for synthesize-while-you-play applications.
|
| 11 |
-
|
| 12 |
-
🔊 **Try it online:** [BlueMagpie-TTS Demo (Hugging Face Space)](https://huggingface.co/spaces/voidful/BlueMagpie-TTS-Demo)
|
| 13 |
-
|
| 14 |
-
## Install
|
| 15 |
|
| 16 |
```bash
|
| 17 |
-
git clone https://github.com/
|
| 18 |
cd BlueMagpie-TTS
|
| 19 |
-
pip install -e .
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
The install pulls in the [`barbet`](https://github.com/OpenFormosa/Barbet)
|
| 23 |
-
package (the text-semantic language model) from GitHub. The acoustic modules are
|
| 24 |
-
vendored in `bluemagpie/_vendor/` (sourced from
|
| 25 |
-
[VoxCPM](https://github.com/OpenBMB/VoxCPM), Apache-2.0) and need no separate
|
| 26 |
-
install. To save synthesized audio, also install `soundfile`:
|
| 27 |
-
|
| 28 |
-
```bash
|
| 29 |
-
pip install soundfile
|
| 30 |
```
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
### From Hugging Face
|
| 35 |
|
| 36 |
```python
|
| 37 |
-
import os
|
| 38 |
from huggingface_hub import snapshot_download
|
| 39 |
-
from transformers import PreTrainedTokenizerFast
|
| 40 |
from bluemagpie import BlueMagpieModel
|
| 41 |
|
| 42 |
model_dir = snapshot_download("OpenFormosa/BlueMagpie-TTS", token=True)
|
| 43 |
-
|
| 44 |
-
tokenizer = PreTrainedTokenizerFast(tokenizer_file=os.path.join(model_dir, "tokenizer.json"))
|
| 45 |
-
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, training=False, device="cuda")
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
### From a local directory
|
| 49 |
-
|
| 50 |
-
```python
|
| 51 |
-
import os
|
| 52 |
-
from transformers import PreTrainedTokenizerFast
|
| 53 |
-
from bluemagpie import BlueMagpieModel
|
| 54 |
-
|
| 55 |
-
model_dir = "checkpoints/bluemagpie"
|
| 56 |
-
tokenizer = PreTrainedTokenizerFast(tokenizer_file=os.path.join(model_dir, "tokenizer.json"))
|
| 57 |
-
model = BlueMagpieModel.from_local(model_dir, tokenizer=tokenizer, training=False, device="cuda")
|
| 58 |
```
|
| 59 |
|
| 60 |
-
|
| 61 |
-
- Always use `training=False` for inference.
|
| 62 |
-
|
| 63 |
-
## Basic synthesis: text to speech
|
| 64 |
-
|
| 65 |
-
`generate` returns a speech waveform (`torch.Tensor`); pair it with `soundfile`
|
| 66 |
-
to write a `.wav`. The output sample rate is `model.sample_rate` (48 kHz).
|
| 67 |
|
| 68 |
```python
|
| 69 |
import soundfile as sf
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
Two ways.
|
| 78 |
-
|
| 79 |
-
**A. Speaker vector (`speaker_centroid`)** — extract a vector from the reference
|
| 80 |
-
audio, then synthesize (no transcript needed):
|
| 81 |
-
|
| 82 |
-
```bash
|
| 83 |
-
pip install -e ".[clone]" # extraction needs speechbrain (ECAPA-TDNN)
|
| 84 |
-
python scripts/extract_speaker_centroid.py --audio reference.wav --out my_voice.pt
|
| 85 |
-
# more clips of the same speaker -> cleaner centroid: --audio a.wav b.wav c.wav
|
| 86 |
-
```
|
| 87 |
-
|
| 88 |
-
```python
|
| 89 |
-
import torch
|
| 90 |
-
|
| 91 |
-
centroid = torch.load("my_voice.pt", weights_only=True) # [192] speaker vector
|
| 92 |
-
audio = model.generate(
|
| 93 |
-
target_text="今天天氣真好。",
|
| 94 |
-
speaker_centroid=centroid,
|
| 95 |
-
cfg_value=2.8,
|
| 96 |
-
)
|
| 97 |
-
|
| 98 |
-
# or extract it in-process:
|
| 99 |
-
from bluemagpie import extract_speaker_centroid
|
| 100 |
-
centroid = extract_speaker_centroid("reference.wav") # [192]
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
**B. Reference clip (`reference_wav_path`)** — pass a reference clip directly:
|
| 104 |
-
|
| 105 |
-
```python
|
| 106 |
-
audio = model.generate(
|
| 107 |
-
target_text="今天天氣真好。",
|
| 108 |
-
reference_wav_path="reference.wav",
|
| 109 |
-
cfg_value=2.8,
|
| 110 |
)
|
|
|
|
| 111 |
```
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
The model bundles a **multi-speaker table** at `checkpoints/speaker_centroids.pt`,
|
| 116 |
-
currently holding two speakers:
|
| 117 |
-
|
| 118 |
-
| speaker id | description | suggested `cfg_value` |
|
| 119 |
-
| --- | --- | --- |
|
| 120 |
-
| `hung_yi_lee` | Prof. Hung-yi Lee's speaker vector (used with his authorization; the official best params are tuned for this speaker) | 2.0–2.8 |
|
| 121 |
-
| `female_voice` | a generic female voice | 2.0–2.8 |
|
| 122 |
-
|
| 123 |
-
The table has the format `{"speaker_ids": [...], "centroids": tensor[N, 192], "dim": 192}`.
|
| 124 |
-
Load it with `torch.load`, **pick a speaker's `[192]` vector by id**, and pass it as
|
| 125 |
-
`speaker_centroid`:
|
| 126 |
|
| 127 |
```python
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
weights_only=True,
|
| 135 |
)
|
| 136 |
-
print(table["speaker_ids"]) # ['hung_yi_lee', 'female_voice']
|
| 137 |
-
|
| 138 |
-
# switch speaker by changing this line ("hung_yi_lee" or "female_voice")
|
| 139 |
-
speaker_id = "female_voice"
|
| 140 |
-
speaker_centroid = table["centroids"][table["speaker_ids"].index(speaker_id)] # [192]
|
| 141 |
|
| 142 |
-
audio
|
| 143 |
-
|
| 144 |
-
|
|
|
|
| 145 |
cfg_value=2.0,
|
|
|
|
| 146 |
)
|
| 147 |
-
```
|
| 148 |
-
|
| 149 |
-
If you only have the model id (haven't `snapshot_download`-ed the whole model yet),
|
| 150 |
-
grab just the table:
|
| 151 |
-
|
| 152 |
-
```python
|
| 153 |
-
from huggingface_hub import hf_hub_download
|
| 154 |
-
|
| 155 |
-
path = hf_hub_download("OpenFormosa/BlueMagpie-TTS", "checkpoints/speaker_centroids.pt")
|
| 156 |
-
table = torch.load(path, map_location="cpu", weights_only=True)
|
| 157 |
-
```
|
| 158 |
|
| 159 |
-
> To add more speakers, extract your own (authorized) `[192]` vector with
|
| 160 |
-
> `extract_speaker_centroid` from the *Voice cloning* section above — it's passed the
|
| 161 |
-
> exact same way. The earlier single-speaker file
|
| 162 |
-
> `checkpoints/hung_yi_lee_speaker_centroids.pt` (same format) is still available.
|
| 163 |
-
|
| 164 |
-
## Streaming output
|
| 165 |
-
|
| 166 |
-
When you need to play while synthesizing, use `generate_streaming`. It is a
|
| 167 |
-
generator that yields audio chunks one at a time:
|
| 168 |
-
|
| 169 |
-
```python
|
| 170 |
-
chunks = []
|
| 171 |
-
for chunk in model.generate_streaming(target_text="今天天氣真好。"):
|
| 172 |
-
chunks.append(chunk)
|
| 173 |
-
# play or write each chunk in real time here
|
| 174 |
-
```
|
| 175 |
-
|
| 176 |
-
> Note: automatic retry (`retry_badcase`) is not supported in streaming mode.
|
| 177 |
-
|
| 178 |
-
## Four input modes
|
| 179 |
-
|
| 180 |
-
The model supports four input combinations through the same `generate` interface:
|
| 181 |
-
|
| 182 |
-
| Mode | Parameters | Use |
|
| 183 |
-
|---|---|---|
|
| 184 |
-
| Plain synthesis | `target_text` | Read the text aloud |
|
| 185 |
-
| Continuation | `target_text`, `prompt_text`, `prompt_wav_path` | Continue from an existing clip and its text |
|
| 186 |
-
| Reference clip | `target_text`, `reference_wav_path` | Mimic the reference speaker's timbre |
|
| 187 |
-
| Speaker vector | `target_text`, `speaker_centroid` | Clone a voice from a speaker vector |
|
| 188 |
-
|
| 189 |
-
## Common `generate` parameters
|
| 190 |
-
|
| 191 |
-
| Parameter | Default | Description |
|
| 192 |
-
|---|---|---|
|
| 193 |
-
| `target_text` | (required) | The text to synthesize |
|
| 194 |
-
| `prompt_text` | `""` | Prompt text, paired with `prompt_wav_path` for continuation |
|
| 195 |
-
| `prompt_wav_path` | `""` | Prompt audio path, for continuation |
|
| 196 |
-
| `reference_wav_path` | `""` | Reference audio path, for voice cloning |
|
| 197 |
-
| `speaker_centroid` | `None` | Speaker vector, to select a timbre |
|
| 198 |
-
| `cfg_value` | `2.0` | Guidance strength; higher follows the condition more closely but can sound less natural |
|
| 199 |
-
| `inference_timesteps` | `10` | Sampling steps; more usually means better quality and slower speed |
|
| 200 |
-
| `min_len` / `max_len` | `2` / `2000` | Lower / upper bound on output length |
|
| 201 |
-
| `retry_badcase` | `False` | Auto-retry on detected bad output (unsupported in streaming) |
|
| 202 |
-
|
| 203 |
-
## Batch serving engine (multi-request acceleration)
|
| 204 |
-
|
| 205 |
-
To serve many synthesis requests at once for higher throughput, use the built-in
|
| 206 |
-
batch engine `BlueMagpieEngine`. It does **continuous batching**: requests are
|
| 207 |
-
decoded together as a batch, new requests can join mid-decode, and they do not
|
| 208 |
-
interfere with one another.
|
| 209 |
-
|
| 210 |
-
Highlights:
|
| 211 |
-
|
| 212 |
-
- **No extra dependencies** — torch only; no vLLM, flash-attn, etc.
|
| 213 |
-
- **Cross-device** — one code path on CUDA, Apple Silicon (MPS), and CPU.
|
| 214 |
-
CUDA-only optimizations are auto-detected and enabled, and skipped elsewhere.
|
| 215 |
-
- **Numerically identical to single-call `generate`** at batch=1 (`model.generate`
|
| 216 |
-
is always the reference).
|
| 217 |
-
|
| 218 |
-
### Basic usage
|
| 219 |
-
|
| 220 |
-
```python
|
| 221 |
-
import soundfile as sf
|
| 222 |
-
from bluemagpie.serving import BlueMagpieEngine, EngineConfig, Request
|
| 223 |
-
|
| 224 |
-
# load `model` and `tokenizer` as shown above (from_local)
|
| 225 |
-
engine = BlueMagpieEngine(model, EngineConfig(max_num_seqs=16))
|
| 226 |
-
|
| 227 |
-
engine.add_request(Request(target_text="今天天氣真好。", seed=0))
|
| 228 |
-
engine.add_request(Request(target_text="第二句話。", reference_wav_path="speaker.wav"))
|
| 229 |
-
|
| 230 |
-
for out in engine.run(): # returned in request-id (submission) order
|
| 231 |
-
# out.audio: 48 kHz waveform (when an AudioVAE is attached); out.latents: [T, p, d]
|
| 232 |
-
sf.write(f"output_{out.request_id}.wav", out.audio.numpy(), out.sample_rate)
|
| 233 |
-
```
|
| 234 |
-
|
| 235 |
-
`Request` supports the same four input modes as `generate` (plain, continuation,
|
| 236 |
-
reference clip, speaker vector) via the fields `target_text`, `prompt_text`,
|
| 237 |
-
`prompt_wav_path`, `reference_wav_path`, `speaker_centroid`, `cfg_value`,
|
| 238 |
-
`inference_timesteps`, etc. Each request may set a `seed`, which makes its output
|
| 239 |
-
independent of how many neighbours share the batch and of admission order.
|
| 240 |
-
|
| 241 |
-
### Streaming
|
| 242 |
-
|
| 243 |
-
`engine.stream()` is a generator that yields a chunk per request per step:
|
| 244 |
-
|
| 245 |
-
```python
|
| 246 |
-
for chunk in engine.stream():
|
| 247 |
-
# chunk.request_id, chunk.latents, chunk.audio, chunk.finished
|
| 248 |
-
play_or_write(chunk)
|
| 249 |
```
|
| 250 |
|
| 251 |
-
|
| 252 |
-
> (`chunk.audio`); prompt-audio continuation streams `latents` only — use `run()`
|
| 253 |
-
> when you need its audio.
|
| 254 |
-
|
| 255 |
-
### Configuration
|
| 256 |
-
|
| 257 |
-
Common `EngineConfig` parameters:
|
| 258 |
-
|
| 259 |
-
| Parameter | Default | Description |
|
| 260 |
-
|---|---|---|
|
| 261 |
-
| `max_num_seqs` | `16` | Max concurrent requests batched together |
|
| 262 |
-
| `max_model_len` | `2048` | Max length per sequence (prompt + generated) |
|
| 263 |
-
| `inference_timesteps` | `9` | Sampling steps |
|
| 264 |
-
| `cfg_value` | `2.8` | Guidance strength |
|
| 265 |
-
| `enforce_eager` | `True` | Keep the path numerically identical to single-call `generate` |
|
| 266 |
-
| `compile` | `False` | Enable `torch.compile` (CUDA only; auto-skipped elsewhere) |
|
| 267 |
-
|
| 268 |
-
> See [`src/bluemagpie/serving/DESIGN.md`](src/bluemagpie/serving/DESIGN.md) for the
|
| 269 |
-
> engine's design, trade-offs, and known limitations.
|
| 270 |
-
|
| 271 |
-
### Why not just use vLLM?
|
| 272 |
-
|
| 273 |
-
People often expect "wrap it in vLLM and it gets fast", but for BlueMagpie that
|
| 274 |
-
does not work, for two reasons:
|
| 275 |
-
|
| 276 |
-
1. **The real compute bottleneck is the diffusion decoder, not the language
|
| 277 |
-
model.** Per generated audio unit the DiT (LocDiT / CFM diffusion decoder) is
|
| 278 |
-
called ~16–18 times (sampling steps × the unconditional/conditional CFG
|
| 279 |
-
pair), while the language models (Barbet, RALM) run once each. vLLM is a
|
| 280 |
-
*text language-model* inference framework — it does not touch the diffusion
|
| 281 |
-
decoder at all, so even moving the LMs onto vLLM leaves the dominant compute
|
| 282 |
-
running eagerly and barely moves end-to-end latency.
|
| 283 |
-
2. **vLLM does not support Barbet's hybrid architecture.** Barbet (the
|
| 284 |
-
text-semantic LM) is a Mamba2 + attention hybrid, and vLLM (as well as
|
| 285 |
-
nano-vllm and vllm-omni) has zero support for such a hybrid TSLM — you'd have
|
| 286 |
-
to implement a first-class hybrid model yourself (large effort, CUDA-only).
|
| 287 |
-
|
| 288 |
-
So this engine **borrows vLLM's architectural techniques without depending on its
|
| 289 |
-
CUDA kernels**:
|
| 290 |
-
|
| 291 |
-
- **Continuous batching** of many requests (the main throughput win), sharing
|
| 292 |
-
batched compute across requests.
|
| 293 |
-
- A **padded KV cache + SDPA + masks** instead of vLLM's PagedAttention /
|
| 294 |
-
FlashAttention — trading peak speed and memory efficiency for cross-device,
|
| 295 |
-
zero-dependency portability.
|
| 296 |
-
- Barbet's Mamba state handled with a **pure-PyTorch single-step recurrence**, no
|
| 297 |
-
fused kernel required.
|
| 298 |
-
- Optional `compile=True` uses `torch.compile` (which captures CUDA graphs
|
| 299 |
-
internally) to accelerate the **DiT and LocEnc** — the actual hot path, and
|
| 300 |
-
exactly what wrapping in vLLM would *not* do for you.
|
| 301 |
-
|
| 302 |
-
> In short: we don't aim to beat vLLM on a single op; we use vLLM-class **batch
|
| 303 |
-
> scheduling** plus **DiT-bottleneck optimization** to raise overall throughput
|
| 304 |
-
> with no extra dependencies, across CUDA / MPS / CPU.
|
| 305 |
-
|
| 306 |
-
## Apple Silicon MLX acceleration (optional)
|
| 307 |
-
|
| 308 |
-
On Apple Silicon (M-series), a native **MLX** path runs inference directly on the
|
| 309 |
-
Apple GPU (Metal, unified memory) — typically faster than PyTorch's MPS backend.
|
| 310 |
-
It is an optional extra; the core package stays torch-only:
|
| 311 |
-
|
| 312 |
-
```bash
|
| 313 |
-
pip install -e .[mlx]
|
| 314 |
-
```
|
| 315 |
|
| 316 |
```python
|
|
|
|
| 317 |
import soundfile as sf
|
| 318 |
-
from bluemagpie import BlueMagpieModel
|
| 319 |
-
from bluemagpie.mlx import BlueMagpieMLX, mlx_generate
|
| 320 |
|
| 321 |
-
|
| 322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
-
|
| 325 |
-
sf.write("
|
| 326 |
```
|
| 327 |
|
| 328 |
-
|
| 329 |
-
decoder**, the AR loop) is re-implemented in MLX and numerically parity-checked,
|
| 330 |
-
module by module — generation can run torch-free (only tokenization and
|
| 331 |
-
reference-wav encoding stay in torch).
|
| 332 |
-
- Decode uses cached single-step kernels (it advances one position per step, not a
|
| 333 |
-
full re-run).
|
| 334 |
-
- `mlx_generate` supports the same four input modes as `generate`.
|
| 335 |
-
- On the real 7.75 GB model: end-to-end **RTF 0.77** (faster than real time) —
|
| 336 |
-
~**1.45×** over torch-MPS and ~**3.27×** over torch-CPU (fp32,
|
| 337 |
-
`scripts/bench_rtf.py`). See [`src/bluemagpie/mlx/DESIGN.md`](src/bluemagpie/mlx/DESIGN.md).
|
| 338 |
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
-
|
| 342 |
-
`from_local`, which is stable on transformers 5.x. (`from_local`'s automatic
|
| 343 |
-
tokenizer loading can fail on 5.x — see Troubleshooting.)
|
| 344 |
-
- A GPU is optional: set `device="cpu"` (slower, but short utterances take only
|
| 345 |
-
tens of seconds). Output is 48 kHz mono.
|
| 346 |
-
- The bundled `hung_yi_lee` speaker vector is authorized for example use. For any
|
| 347 |
-
other speaker or voice cloning, use only reference audio or speaker vectors you
|
| 348 |
-
are authorized to use.
|
| 349 |
-
- Keep speaker-vector tables and synthesized audio private; do not distribute
|
| 350 |
-
them without authorization.
|
| 351 |
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
-
|
| 355 |
-
tokenizer explicitly from `tokenizer.json`, so they work on transformers 5.x with
|
| 356 |
-
no extra steps (the model only uses the tokenizer's `encode`).
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
argument 'fix_mistral_regex'`, or appear to load but raise `ValueError: No
|
| 362 |
-
tokenizer attached to BlueMagpieModel` when you call `generate()`. Use the
|
| 363 |
-
explicit loading shown above instead.
|
|
|
|
| 1 |
+
# BlueMagpie-TTS Usage
|
| 2 |
|
| 3 |
+
This is an inference checkpoint. Install the local package first:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
```bash
|
| 6 |
+
git clone https://github.com/voidful/BlueMagpie-TTS
|
| 7 |
cd BlueMagpie-TTS
|
| 8 |
+
pip install -e ".[train]"
|
| 9 |
+
pip install soundfile huggingface_hub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
```
|
| 11 |
|
| 12 |
+
Download and load:
|
|
|
|
|
|
|
| 13 |
|
| 14 |
```python
|
|
|
|
| 15 |
from huggingface_hub import snapshot_download
|
|
|
|
| 16 |
from bluemagpie import BlueMagpieModel
|
| 17 |
|
| 18 |
model_dir = snapshot_download("OpenFormosa/BlueMagpie-TTS", token=True)
|
| 19 |
+
model = BlueMagpieModel.from_local(model_dir, training=False, device="cuda")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
```
|
| 21 |
|
| 22 |
+
Generate a short utterance:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
```python
|
| 25 |
import soundfile as sf
|
| 26 |
|
| 27 |
+
wav = model.generate(
|
| 28 |
+
target_text="這是合成語音測試。",
|
| 29 |
+
reference_wav_path="reference_speaker.wav",
|
| 30 |
+
cfg_value=2.0,
|
| 31 |
+
inference_timesteps=10,
|
| 32 |
+
retry_badcase=True,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
)
|
| 34 |
+
sf.write("short.wav", wav.detach().cpu().numpy(), model.sample_rate)
|
| 35 |
```
|
| 36 |
|
| 37 |
+
Generation modes:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
```python
|
| 40 |
+
# centroid-only
|
| 41 |
+
wav = model.generate(
|
| 42 |
+
target_text="這是指定 speaker centroid 的測試。",
|
| 43 |
+
speaker_centroid=centroid_tensor,
|
| 44 |
+
cfg_value=2.0,
|
| 45 |
+
inference_timesteps=10,
|
|
|
|
| 46 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
# speaker-reference-audio-only; no reference transcript required
|
| 49 |
+
wav = model.generate(
|
| 50 |
+
target_text="明天 meeting 取消一次,我們大家放假。",
|
| 51 |
+
reference_wav_path="reference_speaker.wav",
|
| 52 |
cfg_value=2.0,
|
| 53 |
+
inference_timesteps=10,
|
| 54 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
```
|
| 57 |
|
| 58 |
+
Streaming:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
```python
|
| 61 |
+
import torch
|
| 62 |
import soundfile as sf
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
chunks = []
|
| 65 |
+
for chunk in model.generate_streaming(
|
| 66 |
+
target_text="這是一段串流語音合成測試。",
|
| 67 |
+
reference_wav_path="reference_speaker.wav",
|
| 68 |
+
cfg_value=2.0,
|
| 69 |
+
inference_timesteps=10,
|
| 70 |
+
):
|
| 71 |
+
chunks.append(chunk.detach().cpu())
|
| 72 |
|
| 73 |
+
wav = torch.cat(chunks, dim=-1)
|
| 74 |
+
sf.write("streaming.wav", wav.numpy(), model.sample_rate)
|
| 75 |
```
|
| 76 |
|
| 77 |
+
Long text:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
1. Split the text by sentence or paragraph.
|
| 80 |
+
2. Generate each chunk separately.
|
| 81 |
+
3. Concatenate the waveforms with a short crossfade if needed.
|
| 82 |
+
4. Keep the same centroid or reference audio across chunks to reduce speaker
|
| 83 |
+
drift.
|
| 84 |
|
| 85 |
+
Recommended generation defaults:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
+
- `cfg_value=2.0`
|
| 88 |
+
- `inference_timesteps=10`
|
| 89 |
+
- `retry_badcase=True` for offline generation
|
| 90 |
+
- `max_len=2000` unless a longer chunk is intentionally needed
|
| 91 |
+
- Reference wavs for `reference_wav_path=...` should be at least 3 seconds.
|
| 92 |
|
| 93 |
+
Safety:
|
|
|
|
|
|
|
| 94 |
|
| 95 |
+
- Use only rights-cleared reference audio or speaker embeddings.
|
| 96 |
+
- Do not present generated speech as a real person or real notification unless
|
| 97 |
+
that is explicitly authorized and reviewed.
|
|
|
|
|
|
|
|
|
checkpoints/hung_yi_lee_speaker_centroids.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:e1d4c95a4c33935ff1fee0ab47fa796dcc13908a183c60e9b02bc0a61c541c4c
|
| 3 |
-
size 2691
|
|
|
|
|
|
|
|
|
|
|
|
checkpoints/speaker_centroids.pt
DELETED
|
Binary file (3.31 kB)
|
|
|
config.json
CHANGED
|
@@ -286,6 +286,7 @@
|
|
| 286 |
"rms_norm_eps": 1e-06
|
| 287 |
},
|
| 288 |
"speaker_embed_dim": 192,
|
|
|
|
| 289 |
"audio_start_token": -1,
|
| 290 |
"audio_end_token": -1,
|
| 291 |
"ref_audio_start_token": -1,
|
|
@@ -294,31 +295,5 @@
|
|
| 294 |
"barbet_effective_vocab_size": null,
|
| 295 |
"max_length": 8192,
|
| 296 |
"device": "cuda",
|
| 297 |
-
"dtype": "bfloat16"
|
| 298 |
-
"generation_defaults": {
|
| 299 |
-
"cfg_value": 2.8,
|
| 300 |
-
"inference_timesteps": 9,
|
| 301 |
-
"max_len": 2000,
|
| 302 |
-
"retry_badcase": true,
|
| 303 |
-
"retry_badcase_max_times": 3,
|
| 304 |
-
"retry_badcase_ratio_threshold": 6.0,
|
| 305 |
-
"speaker_id": "hung_yi_lee",
|
| 306 |
-
"speaker_source_dataset": "voidful/hung-yi_lee",
|
| 307 |
-
"speaker_centroid_path": "checkpoints/hung_yi_lee_speaker_centroids.pt",
|
| 308 |
-
"speaker_centroid_sha256": "e1d4c95a4c33935ff1fee0ab47fa796dcc13908a183c60e9b02bc0a61c541c4c",
|
| 309 |
-
"speaker_centroid_dim": 192
|
| 310 |
-
},
|
| 311 |
-
"generation_defaults_source": {
|
| 312 |
-
"scope": "tts_hard_sentences_zh_500 + Breeze-ASR-25 normalized CER",
|
| 313 |
-
"sentences": "/home/voidful/tts_hard_sentences_zh_500.txt",
|
| 314 |
-
"asr_model": "MediaTek-Research/Breeze-ASR-25",
|
| 315 |
-
"conversion": "s2twp",
|
| 316 |
-
"normalized_cer": 0.09669792733863977,
|
| 317 |
-
"mixed_token_error_rate": 0.0911015155363644,
|
| 318 |
-
"char_errors": 1227,
|
| 319 |
-
"char_reference_length": 12689,
|
| 320 |
-
"evaluated_examples": 500,
|
| 321 |
-
"trial": "hy_cfg2p8_steps9",
|
| 322 |
-
"run": "hungyi_high_refine_hy_cfg2p8_steps9_20260620"
|
| 323 |
-
}
|
| 324 |
}
|
|
|
|
| 286 |
"rms_norm_eps": 1e-06
|
| 287 |
},
|
| 288 |
"speaker_embed_dim": 192,
|
| 289 |
+
"ref_audio_bottleneck": "none",
|
| 290 |
"audio_start_token": -1,
|
| 291 |
"audio_end_token": -1,
|
| 292 |
"ref_audio_start_token": -1,
|
|
|
|
| 295 |
"barbet_effective_vocab_size": null,
|
| 296 |
"max_length": 8192,
|
| 297 |
"device": "cuda",
|
| 298 |
+
"dtype": "bfloat16"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}
|
pytorch_model.bin
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 7746547131
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e454c077ded2ce9d5f3b8ec4f336af840a2fe9912149d1f25afd4938bf7a0319
|
| 3 |
size 7746547131
|
release_metadata.json
CHANGED
|
@@ -1,45 +1,37 @@
|
|
| 1 |
{
|
| 2 |
"format": "bluemagpie-pretrained",
|
| 3 |
-
"checkpoint": "
|
| 4 |
"files": [
|
| 5 |
"pytorch_model.bin",
|
| 6 |
"audiovae.pth",
|
| 7 |
"config.json",
|
| 8 |
"tokenizer.json",
|
| 9 |
-
"tokenizer_config.json"
|
| 10 |
-
"checkpoints/hung_yi_lee_speaker_centroids.pt"
|
| 11 |
],
|
| 12 |
"evaluation": {
|
| 13 |
"scope": "internal held-out evaluation",
|
| 14 |
-
"
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
"
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
"
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
"
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
"normalized_cer": 0.09669792733863977,
|
| 38 |
-
"mixed_token_error_rate": 0.0911015155363644,
|
| 39 |
-
"char_errors": 1227,
|
| 40 |
-
"char_reference_length": 12689,
|
| 41 |
-
"evaluated_examples": 500,
|
| 42 |
-
"trial": "hy_cfg2p8_steps9",
|
| 43 |
-
"run": "hungyi_high_refine_hy_cfg2p8_steps9_20260620"
|
| 44 |
}
|
| 45 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"format": "bluemagpie-pretrained",
|
| 3 |
+
"checkpoint": "step_0006000",
|
| 4 |
"files": [
|
| 5 |
"pytorch_model.bin",
|
| 6 |
"audiovae.pth",
|
| 7 |
"config.json",
|
| 8 |
"tokenizer.json",
|
| 9 |
+
"tokenizer_config.json"
|
|
|
|
| 10 |
],
|
| 11 |
"evaluation": {
|
| 12 |
"scope": "internal held-out evaluation",
|
| 13 |
+
"centroid_only": {
|
| 14 |
+
"cer": 0.07441389020336389,
|
| 15 |
+
"wer": 0.08565566378066376
|
| 16 |
+
},
|
| 17 |
+
"speaker_reference_audio_only": {
|
| 18 |
+
"cer": 0.08992608978902839,
|
| 19 |
+
"wer": 0.11774568024568023
|
| 20 |
+
},
|
| 21 |
+
"long_centroid_only": {
|
| 22 |
+
"cer": 0.07511737089201878,
|
| 23 |
+
"wer": 0.07511737089201878,
|
| 24 |
+
"chars_per_second": 4.017351942663145
|
| 25 |
+
},
|
| 26 |
+
"long_speaker_reference_audio_only": {
|
| 27 |
+
"cer": 0.0892018779342723,
|
| 28 |
+
"wer": 0.0892018779342723,
|
| 29 |
+
"chars_per_second": 4.017351942663145,
|
| 30 |
+
"chunk_speaker_similarity_drop": 0.1085478663444519
|
| 31 |
+
},
|
| 32 |
+
"reference_baseline": {
|
| 33 |
+
"cer": 0.114483,
|
| 34 |
+
"wer": 0.148266
|
| 35 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
}
|