同一天做了三套語音對話迴圈,只為了換掉中間那一段
對著麥克風講話,AI 用聲音回你。前後兩段都一樣,中間的 TTS 換成 CosyVoice3、Kokoro、F5-TTS 各做一套。三套在同一台機器上用同樣三句話重量了一次,量完發現用 RTF 排名次會被語速灌水。

8 月 26 日凌晨 00:26,我開了一個 repo 叫 voice-loop,做的事一句話講得完:對著麥克風講話,AI 用你自己的聲音回答你。當天下午 13:36,我在同一分鐘又開了另外兩個 repo。
三套的前後完全一樣:arecord 錄音、whisper.cpp 在本機 GPU 轉文字、丟給雲端 LLM 要一句簡短的回答、合成、paplay 播出來。整條線裡只有產生回答那一段會連外網。
換掉的只有中間那個合成引擎。會做三套,是因為 TTS 選型這件事看規格表沒有用。
這篇是寫給要在自己機器上跑語音的人看的。如果你正在猶豫要裝哪一套,下面是三條路各自的代價。
為什麼是這三套
| 引擎 | 一句話 | |
|---|---|---|
| voice-loop | CosyVoice3 0.5B | 能 zero-shot 複製你的聲音 |
| kokoro-voice-loop | Kokoro-82M ONNX | 純 CPU,不吃顯存 |
| f5-voice-loop | F5-TTS | 非自回歸,不吞字 |
第一套的賣點是聲音。不給任何參數的時候,它拿你剛剛講的那句話當聲音樣本,每一輪都換。所以你完全不用事先準備錄音檔,開起來講一句,它就用那個聲音回你。想要穩定一點就 --record-voice 開場錄一段整場沿用,或者直接 --voice 檔案.wav 指定。參考文字都不用自己打,不給參數時直接用 STT 的結果。
第二套是為了 8 GB 顯存那台。Kokoro 走 ONNX Runtime 的 CPU 引擎,模型 300 MB,顯存用量是 0。顯卡跑滿 100% 也不影響語音生成,這在同時要跑本機 LLM 的時候很有感。RTF 大約 0.1。
第三套是折衷。F5-TTS 約 2.1 GB 顯存,能跟 llama-server 和 whisper-server 同時常駐。它用 Flow Matching,語音長度跟文字線性對齊,長句跟生僻字不會吞字尾或跳針。
真正的分岔點:台灣腔要在哪一層修
三套都要處理同一個問題。開源中文 TTS 幾乎都是大陸語料訓練的,直接唸出來是大陸腔:垃圾唸 lājī、我和你的和唸 hé、品質唸 zhì、星期的期唸一聲。
我原本以為這是同一份修正檔到處複製的事。實際做完發現要分兩種。
f5-voice-loop 跟 voice-loop 的 taiwanize.py 是同一份檔案,md5sum 一模一樣。它改的是字:
("垃圾", "勒瑟"),
("我和你", "我汗你"),
("企業", "氣業"),
("品質", "品直"),
("星期", "星旗"),
送進引擎之前先把字換掉,引擎照著唸就對了。這招在 CosyVoice3 跟 F5-TTS 上都能用,因為它們吃的是中文字。
kokoro-voice-loop 那份不一樣,行數也不同。Kokoro 前面接的是 misaki[zh] 的 G2P,中文先變成帶聲調的音素才進模型。所以台灣化要在音素層做,改的是 IPA:捲舌 ʂ 換成平舌 s、ʈʂ 換成 ts、ʈʂʰ 換成 tsʰ。連「微、問、我」被唸成大陸北方那種齒唇音 vei/ven,都是強制把 w 開頭轉成純雙唇的 u 解掉的。
改字的那套方法,在音素引擎上完全沒有用,反過來也是。這件事在規格表上看不出來,要做到那一步才會撞到。
選 TTS 引擎的時候,「它吃字還是吃音素」決定了你之後所有的發音修正工作長什麼樣,這個影響比音質分數大得多。
量給自己看
原本這篇寫到這裡是攤手的:三個 repo 的數字各是各的,我沒有在同一台機器上重量過。那樣寫等於沒寫,所以補了。
同一台 RTX 4060 Laptop 8GB、同一份 10.45 秒參考音、同樣三句話(3 字、23 字、66 字)、暖機一次之後計時三次取中位數。走的是三個 repo 自己的合成呼叫,不是我另外寫一套。
| 引擎 | 3 字 | 23 字 | 66 字 | 顯存 | 載入 |
|---|---|---|---|---|---|
| Kokoro-82M(純 CPU) | 0.47s | 1.53s | 3.95s | 0(RAM 1.1 GB) | 1.2s |
| F5-TTS(GPU, NFE=12) | 1.04s | 1.22s | 1.99s | 1.5 GB | 6.4s |
| CosyVoice 3(GPU, 聲紋快取) | 0.89s | 1.95s | 4.13s | 5.5 GB | 21.6s |
| CosyVoice 3(每輪重算聲紋) | 2.24s | 3.14s | 5.28s | 5.5 GB | 21.6s |
量完之後,前面那些「賣點」有兩個要改口。
第一,F5 的耗時曲線幾乎是平的。文字從 3 字變 66 字,長了 22 倍,它的耗時只從 1.04 秒變 1.99 秒,多不到一倍。同樣這段路 Kokoro 多了 8.4 倍、CosyVoice 多了 4.6 倍。所以短句 Kokoro 最快,到 23 字 F5 就反超,長句 F5 快到兩倍。非自回歸不只是規格表上的名詞,代價曲線真的是平的。
第二,不要用 RTF 排名次,它會被語速灌水。同樣那 66 字,Kokoro 唸出 15.5 秒的音訊,F5 只有 10.4 秒。RTF 是耗時除以音訊長度,唸得慢的分母大,分數自然好看。Kokoro 的 RTF 看起來贏 F5(0.25 對 0.19 之外的兩檔都是),可是使用者實際等的是合成耗時,那一欄 F5 在中長句都贏。要比「等多久」就看秒數,別看 RTF。
順帶一提,kokoro-voice-loop 的 README 寫 RTF 約 0.1,實測是 0.25 到 0.49,那個數字是高估的,我會去修。
第三個是意外收穫:CosyVoice 的聲紋快取每輪省 1.2 秒。它的 add_zero_shot_spk 可以把參考音的聲紋算一次存起來。開快取跟不開快取,三檔分別差 1.35、1.19、1.15 秒(程式註解裡寫「10 秒參考音每輪要 1.4 秒」,量出來略少一點)。差別大到會翻案:不開快取的時候短句要 2.24 秒才產出 1.0 秒的音訊,追不上即時;開了快取變 0.89 秒對 1.48 秒,追得上。同一個模型,一個設定決定它能不能拿來講電話。
代價是快取只有在參考音固定的時候能用。voice-loop 預設拿你剛剛講的那句話當樣本,每輪都換,那就沒得快取。想要低延遲就得放棄「每輪換聲音」這個賣點。
顯存那欄也順便釐清了。CosyVoice 這次量到 nvidia-smi 程序峰值 5.5 GB、torch 自報 allocated 4.34 GiB;F5 是 1.5 GB 對 1.94 GiB。同一件事有兩個數字,比之前那兩份 README 各說各話的狀況清楚多了。
我還是沒做的一件事:音質盲測。哪一套聽起來比較像人,要有人閉著眼睛聽才算數,那是下一件事。
順帶學到的
一輪對話大約 4 到 9 秒:STT 約 0.6 秒、LLM 0.4 到 2 秒、合成 2 到 6 秒。瓶頸在合成,所以上面那張表才值得花時間量。
whisper.cpp 一定要編 CUDA 版(-DGGML_CUDA=ON),不然會安靜地掉回 CPU 跑,你只會覺得「怎麼有點慢」。
三套都附 setup.sh,缺什麼會直接講清楚要補什麼,不用讀 README 猜。
分工線
要做幾套、每一套要回答什麼問題、台灣腔的判準、什麼叫做「量過了」,是我定的。三個 repo 的程式碼、taiwanize.py 的兩套規則、安裝檢查腳本、那支跑三套的量測腳本,是 AI 寫的。
上面那張表是 2026-08-28 在我自己這台機器上跑出來的,量測腳本走的是三個 repo 各自的合成呼叫,不是另外寫一套來量。
三個 repo 都是公開的:https://github.com/yazelin/voice-loop、https://github.com/yazelin/kokoro-voice-loop、https://github.com/yazelin/f5-voice-loop。要先看發音修正這件事怎麼在別的專案上處理的,寫在這裡:給赤壁 3D 加上電影級音訊。