
繼續下一個組件:聽話,講話,創造聲音。
前面那台 amd 雙 GPU,載入 LLM 之後,記憶體就滿了。 所以配置另外一臺 Proxmox,新增個 ubuntu VM :ip: 192.168.0.31,過程都是一樣。就不重述。
網域先確定有設好:
stt.iron30.com
tts1.iron30.com
tts2.iron30.com
npm 分別指向:
stt.iron30.com 指向 192.168.0.31 , 8080
tts1.iron30.com 指向 192.168.0.31 , 8081
tts2.iron30.com 指向 192.168.0.31 , 8082
因為我們要兩個不同版本的 tts, 一個用地端模型,一個用雲端模型。所以弄兩個 tts。
stt 沒有懸念: whisper.
ssh 連到 hq,切換到 /opt/claude-working,執行 claude code:
告訴 claude code :
連接到 192.168.0.31,在.31 執行:
- 確認:兩張顯示卡都看得到、記憶體容量對。
- 做完把連線資訊寫進這台虛擬機的 Proxmox 備註欄,
並且在 machines.md 和 machines/npm.md 補上這台。
install model : use docker
1. stt 語音轉文字
- whisper.cpp 的 server 版本,用 GPU0。
- ⛔ 它沒有任何認證,誰打得到誰就能用:只聽本機,不要對外開埠。
8080 留給等一下要做的那層前台,由前台去打它。
- 模型用 large-v3-turbo,放資料碟,不要放系統碟。
- 開 OpenAI 相容的那組路徑,之後接東西進來不用另外寫轉接。
- 格式轉換交給它自己做,不要求上傳端先轉好檔。
- ⚠️ 自己編 image 的話:要指定這張卡的運算架構版本,
而且要把顯示記憶體虛擬管理那個選項關掉 —— 容器裡沒有驅動那顆函式庫,
沒關的話最後連結會失敗,而錯誤訊息只會說某幾個符號找不到,不會告訴你原因。
2. tts1: 裝文字轉語音,use edge tts , 不用到 gpu
- 純 CPU,容器跑,綁 8081。
- 台灣的那幾個語者先固定下來,語速、音高、音量都要能調。
- ⚠️ 參數格式很挑:不帶正負號的寫法會被整包拒絕,
預設值要寫成 +0% 這種形式,自己包一層正規化再送出去。
- 要能輸出字幕檔。
- 可以設定斷句閥值,預設:0.475s
- 字典功能:同音字輸出取代。
- 檔案列表,試聽,下載。
3. tts2:裝文字轉語音,use CosyVoice3 use gpu
- GPU,綁 8082。
- 合成是長工作:收到請求先回一個工作編號,讓對方自己來問進度,
不要讓它連著線等到好。前面放一個隊列,不要讓請求直接打進模型。
- 要支援零樣本克隆:上傳一段十秒左右的參考音檔,加上那段音檔的逐字內容,
之後就能用那個聲音唸任何一段文字。
- ⚠️ 模型載入要一到兩分鐘,第一次還要多花約一分鐘暖機,不要以為它掛了就重啟。
- ⚠️ 這包的相依關係很脆:GPU 版的推論函式庫要跟系統的加速函式庫大版本對得上,
對不上它不會報錯,會安靜地退回處理器算。
裝完順便確認數值運算套件沒有被連帶升上去,那會把另一批元件弄壞。
- ⚠️ 下載完先驗這包真的是第三代:看設定檔的檔名、切分器的版本、
有沒有那個額外的強化學習權重檔。⛔ 目錄名字不算證據。
三、⛔ 吃顯示卡的那兩個(stt 和 tts2)都要指定GPU0,
而且指定的方式只有一種算數:在容器的裝置設定裡,讓它只看得到那一張。
⚠️ 工作行程要開幾個,得跟你真的給它的卡數一致。
規則:
- 3個服務都設成開機自動起來。
- 做完把這台寫進 machines.md 和 machines/voice.md,
並且在 Proxmox 的備註欄記下連線資訊。
3個服務都起來之後,先不要急著接東西進去。
告訴 claude code:
幫我量這三個服務。
⛔ 不要只確認「有出文字」「有出聲音」就算過。
一、語音轉文字:
- 找一段真實的錄音,裡面要有人名、公司名這種專有名詞。
⛔ 不要用乾淨的示範音檔,那種東西誰跑都會過。
- 量兩個數字:音檔多長、轉完花多久,算出倍率。
- 逐字稿裡的專有名詞逐個對一次,錯的抄下來給我。
二、兩個文字轉語音,用同一段文字各跑一次,數字並排給我:
- 合成一段一分鐘左右的音檔,算「合成花的時間 ÷ 音檔長度」。
這個值大於一,就是比即時還慢。
- ⚠️ 地端那個比即時慢,通常不是模型慢,是某個零件掉回處理器在算。
驗法:合成的時候看那張卡的記憶體和使用率有沒有動,
沒動就是根本沒在用它。
- 雲端那個要另外量穩定度:同一段文字連打二十次,
記失敗幾次、最慢的一次多久。⛔ 它不在我們機房裡,
穩定度只能量,不能假設。
- 兩邊都輸出一份字幕檔:檔案大小不可以是 0,
打開看時間軸切到多細(一句一格,還是一個詞一格)。
- 音質也記一下:取樣率和位元率各是多少。
三、⚠️ 順便驗一件事:確認跑起來的模型真的是你以為的那一版。
去看檔案本身 —— 設定檔的名稱、權重檔的清單、下載來源。
⛔ 目錄名字不算證據。
回報:語音轉文字的倍率、兩個語音合成的即時率、兩張卡各用掉多少記憶體、
雲端那個失敗幾次、兩份字幕檔的大小與顆粒度、專有名詞錯了哪幾個。
聽的部分,量出來的數字是這樣:
一段 11 秒的錄音,0.4 秒轉完,大約是實際長度的 27 倍速,顯示記憶體只用掉 1.7 GB。
這個數字有個很實際的意義:一小時的會議錄音,兩分鐘就可以出逐字稿。現在,這台在自己家裡的機器就做得到,而且沒有任何一段音檔離開過這個機房。主控權,安全性,全在自家手上。你應該不會想要把內部討論價格,市場競爭策略,人事調整,這種會議錄音,送上雲端或交給外人處理吧!?
說的部份:
用同一段兩百多字的稿子、同一個語者名字,Edge TTS 跟 CosyVoice3 各唸一次:

先看生成時間:
本地端這台花了 33.9 秒,即時率 0.65 —— 比當初修好時又好一些,但那三十幾秒是自己的顯示卡被佔住三十幾秒。
雲端只花了 1.03 秒就回來了,即時率 0.021,連打二十次沒有一次失敗,最慢的一次 1.15 秒。差距三十三倍,而且電費是別人付的。
音質的差距比想像中小:兩邊都是 24 kHz,只差在位元率,74 對 48。
真正把兩套分開的是字幕:雲端那份是繁體、標點完整、按句子邊界切;本地端那份送進去是繁體、吐回來是簡體,六格字幕裡有五十八個簡體字,標點全部消失,而且切點是按比例估算的,第一格從零切到十點五秒,橫跨兩句半。簡單講:本地端自己生產的字幕不能用,要加上後續處理。沒辦法,對岸來的模型。
兩套都留著有它的道理:我們的文件內容能不能公開?
可以公開的、要快、要便宜,走雲端;不想被公開的,走自己的機器。
同一張卡上兩個服務可以共存,是因為它們的胃口差很多:聽的那邊只吃 1.7 GB,說的那邊要吃 8 GB 左右。所以規劃的時候不能只數「有幾個服務」,要把每個服務實際吃掉多少寫下來再相加。
地端那個說話的服務,CosyVoice3,發生過一個問題:合成一段音檔要花的時間,是那段音檔本身長度的 1.29 倍。使用者等待的時間,比他要聽的那段音檔還長。而整個過程沒有任何一行錯誤訊息。原因是:幾個做推論的元件因為加速函式庫的版本不對,安靜地,退回用處理器運算,沒用GPU。修好之後是 0.94,終於比即時快。會明顯出現錯誤的問題最便宜,你至少知道要去修哪裡;不會報錯、就只是變慢的那種錯誤,很貴,很麻煩。
修這件事的過程本身也值得記一筆:把推論函式庫換成對得上的版本之後,它順手把數值運算套件升到新的版本,而另一批元件是用舊版編出來的,當場換一個地方壞掉,得再把數值套件壓回去。本地跑 AI 服務最容易被低估的維護成本就在這裡:一個安裝指令會拉動一整排相依套件,而它們彼此的版本要求並不一致。
還有一件事更難發現。那個CosyVoice3的目錄名字寫的是第三代,但下載下來的內容其實是第二代,而且就這樣跑了三個多月沒察覺。載入器讀到舊版的設定檔,優雅地把它載進來,一句話都沒說。版本號在目錄名字上,不在檔案裡面 —— 而那個名字,是我自己打上去的。
測試好了,數字也都對OK,來把它接出去。
告訴 claude code:
到 NPM 主機:
- 確認 stt.iron30.com tts1.iron30.com tts2.iron30.com從外面解得到 1.2.3.4,解不到就停下來告訴我。
- 設定: stt.iron30.com,指向 192.168.0.31:8080
- 設定: tts1.iron30.com,指向 192.168.0.31:8081
- 設定: tts2.iron30.com,指向 192.168.0.31:8082
- 設定 這三個的憑證。
到 192.168.0.31:
語音轉文字服務
⛔ 它不可以直接掛上網域。做一層有登入的前台綁在 8080,
只有這層碰得到它,網域指過來的是這層。
前台要有:
- 上傳音檔,以及瀏覽器直接錄音。
- 選語言、選輸出格式:純文字或字幕檔。
- 一個「已知詞彙」欄位,使用者可以自己填,也可以由系統帶進來。
那個欄位是準確率的主要槓桿,流程照這個做:
- 先不帶任何名單轉一次,拿到一份粗稿。
- 拿粗稿去跟系統裡已知的名字比對,挑出最像的那幾個,五六個就好。
- 只把挑出來的那幾個帶進去,重轉第二次。
- ⚠️ 如果第二次的結果明顯變短或變破碎,就把第一次的還給使用者。
⛔ 不要把整份名單倒進去。那個欄位很短,塞爆了它會安靜地壞掉。
另外兩個合成的服務:對外那一層至少要帶一把憑證,沒帶的一律擋掉。
安全與限制:
- 密碼只存雜湊,不要存明文;產雜湊的時候不要經過命令列參數。
- ⚠️ 秘密字串放進環境變數檔之前,先確認你的寫法不會被展開;
放完進容器裡確認那個字串的長度對不對。
- 上傳大小上限和逾時要一起放大 —— 音檔比網頁大很多,
預設值會在使用者傳一段會議錄音的時候把他擋下來。
- 登入失敗同一個來源連續幾次就鎖一段時間。
做完把對外開了什麼寫進 exposure.md,帳號密碼寫進 secrets.md。
做好的樣子如下:沒有加入版面配色控制。
因為基本上,這幾個服務會都是AI在使用,不是人在用,所以介面就不那麼重要了。
STT :

TTS1 : Edge TTS
TTS2:CosyVoice3
聽的那一邊,準確率有一個很便宜的槓桿,而且效果大到會讓人以為換了模型。同一段 40 秒的業務口述,不給任何提示的時候,逐字稿是簡體字,而且客戶名稱全部聽成同音字:永盛五金行變成永勝五金行,光華電子變成光華店子,決標變成絕標。把已知的客戶名單先給它之後,同一個音檔,上面那些字全部正確,而且輸出變成繁體。同音字的錯誤事後是救不回來的:那些都是合法的中文詞,拼字檢查看不出來。
但這個槓桿有一個很硬的上限,而且它不會告訴你。名單從 9 個名字加到 15 個之後,同一個音檔的逐字稿當場崩解:句子斷成碎片、詞卡住一直重複,而且退回簡體。它不會回報錯誤,只會給你一份看起來很像逐字稿的東西。所以那個欄位要當成「很短的提示」來用,不是當成「把知識庫倒進去的地方」。
前台那一層自己也踩了一個很經典的坑。密碼的雜湊值裡面有錢字號,而容器的環境變數檔會把錢字號當成變數展開,進到容器裡就只剩前面半截。容器起得來,健康檢查是綠的,所有監控都說一切正常,只有一件事不成立:沒有人登得進去。
語音服務做起來之後有一件事跟預期不一樣:用它的幾乎不是人。翻了幾天的紀錄,使用這台語音服務的,是另外五台機器上的服務 —— 寫作平台把文章唸成語音檔、影片平台配旁白、agent 回訊息的時候順便講一句、還有兩個是自己做的小工具。真人打開網頁,使用服務的次數,反而是裡面最少的。所以這種服務停掉的時候,你不會收到抱怨。你會收到的是五個看起來不相干的功能同時壞掉,而且沒有一個會告訴你原因是這台。內部服務要當成別人的相依套件看待:動它之前先問清楚誰在用,以及它停掉的時候,那五個地方會用什麼姿勢壞掉。
最後是一件跟技術無關的事。現在的合成模型是零樣本克隆:給它十秒鐘的參考音檔,它就能用那個人的聲音唸任何一段文字。不用訓練,不用微調,十秒鐘。所以這件事的門檻已經不在技術那一側了 —— 你能不能用一個聲音,取決於你有沒有那個人的同意,不取決於你做不做得到。
到這裡,這座機房聽得到、也說得出來了。但它還是不知道任何事情:所有的知識都還躺在硬碟裡那一堆檔案裡面。
明天,就從那一堆檔案下手:掃描 PDF,圖檔,讀取檔案,讓機器自己看,變成可以查詢的知識。