你想做一個會聽、會說的本機應用,第一個直覺可能是:錄音先送雲端 ASR,文字處理完,再呼叫雲端 TTS。但 2026 年 8 月,NVIDIA 開源的 NeMo-Speech.cpp 提供了另一條路。它在 LocalLLaMA 的發布討論於 8 月 8 日擷取時約有 220 分、31 則留言;討論裡出現手機與 Raspberry Pi 能否實用、wake word 常駐耗電、延遲與辨識品質等問題,其中最高熱度分支是 wake word。
這篇 NeMo-Speech.cpp 教學就是為第一次碰本機語音模型的讀者寫:我們會從 source build、官方 WAV smoke test 一路做到 HTTP server、OpenAI Python SDK、curl、即時 WebSocket,最後用英文、中文、白噪音與一分鐘長音檔看實際錯誤率和延遲。你不必先懂 C++,只要會貼終端機指令即可。
先把期待放對:本文測的是 2026 年 8 月 6 日的 main commit 5be7bfb。原始碼版本欄位雖寫 1.0.0,查核時官方 GitHub Releases 與 Tags 都是空的,沒有 binary release。它相容的是 OpenAI Audio API 的一個子集合,WebSocket 也只有即時轉錄,不是完整 OpenAI Realtime,更不是已組好的語音助理。
NeMo-Speech.cpp 教學先說結論:你真正會做出什麼?
- 會完成:WAV 轉文字、文字轉 WAV、
/v1/audio/transcriptions、/v1/audio/speech,以及 PCM16 即時轉錄。 - 可以沿用:OpenAI Python/JavaScript SDK 的部分 audio 呼叫;SDK 需要的
model="default"目前只是相容欄位,真正模型在 server 啟動時決定。 - 還要整合:wake-word detector 需另接;若要 VAD,需另載 Silero VAD GGUF;endpointing 預設關閉,可用 token-silence 或搭配 VAD,兩者都要設定並接上 turn-taking。另外還有 LLM、播放佇列、barge-in(使用者插話時打斷播放)、echo control 與應用生命週期。
- 適合用途:本機原型、離線工具、內網整合與開發測試。官方 server 文件把 production 支援路線指向 NVIDIA NIM,因此不要把這個預設 server 直接當公開網路服務。
記住這句就夠了:本機語音 API = ASR(耳朵)+ TTS(嘴巴)+ Server(插座);完整語音助理則還要再加喚醒、思考與播放控制。

NeMo-Speech.cpp 是什麼?先把三個零件看懂
① ASR:把聲音變成字的「耳朵」
ASR(Automatic Speech Recognition,自動語音辨識)吃進 WAV 或麥克風 PCM,吐出文字。官方 quick start 用的是 English-only Nemotron 0.6B Q8 GGUF;中文不應沿用同一個模型,請換成 Nemotron 3.5 multilingual,語言碼是 zh-CN。如果你想先補「推論引擎為何能在本機跑模型」這個觀念,可讀 LLM inference engine 白話教學。
② TTS:Magpie 先想聲音,NanoCodec 再把它解碼
TTS(Text-to-Speech,文字轉語音)不是「再下載一個 Q8」而已。Magpie 產生語音 token,NanoCodec 把 token 解成聲波,還要另外解壓 tokenizer 資產。白話比喻:Magpie 像作曲,NanoCodec 像演奏;少任何一個都不會有 WAV。
③ Server:把本機模型換成應用看得懂的「插座」
nemo-speech serve 預設只綁 127.0.0.1:8080,同時提供 browser playground、REST 與 WebSocket。它的價值不只是少打一條 CLI,而是讓 Python、JavaScript、curl,甚至 C# 的 HttpClient 都能用同一份 HTTP contract;但這不等於 NVIDIA 已驗證官方 C# SDK。
安裝前先準備:本文的可重現測試環境
本文實測機是 Mac mini(Apple M4 10 核、16GB RAM、macOS 26.1),backend 用 Metal;OpenAI Python SDK 是 2.48.0。官方 build system 另有 CPU、CUDA 與 Vulkan preset,但「有 preset」不代表每個算子都在同一裝置,也不代表延遲可直接套用到你的電腦。
README 的直接安裝路徑是 scripts/install.sh --source。不過 source installer 在 detached HEAD 取不到 branch 時,會改 clone 此 checkout 的 local main branch,而不是 detached commit,所以「先 checkout SHA 再跑 installer」不是可靠的版本鎖定。本文為了讓數據對得上,直接從 pinned checkout 手動 build。
# Apple Silicon:安裝本文實際需要的 build 依賴
brew install cmake ninja sentencepiece abseil
git clone https://github.com/NVIDIA/NeMo-Speech.cpp.git
cd NeMo-Speech.cpp
git checkout 5be7bfb104802131e61fe679b3f1401b27270216
git submodule update --init ggml third_party/cpp-httplib
scripts/configure.sh metal-server
cmake --build --preset metal-server -j 8
./build/metal-server/bin/nemo-speech --version
./build/metal-server/bin/nemo-speech doctor --json
本文 M4/Homebrew 第一次 build 碰到 absl::Status undefined symbol;只有出現相同錯誤時,才重新 configure 並再 build:
scripts/configure.sh metal-server \
"-DCMAKE_SHARED_LINKER_FLAGS=-L$(brew --prefix abseil)/lib -labsl_status"
cmake --build --preset metal-server -j 8
Linux/NVIDIA GPU 請依官方 build 文件改成 cpu-server 或 cuda-server。這個專案非常早期,先看懂錯誤再抄 workaround,比把所有平台塞進同一條「萬用指令」可靠。
NeMo-Speech.cpp 教學實作一:下載 Q8,先跑官方 WAV smoke test
python3 -m venv .venv
source .venv/bin/activate
python -m pip install 'huggingface_hub==1.8.0' \
'openai==2.48.0' 'websockets==15.0.1'
hf download nvidia/nemotron-speech-streaming-en-0.6b \
nemotron-speech-streaming-en-0.6b.q8_0.gguf \
--revision ebe59e5a817142986528bbbee5dba8db7b38ed50 \
--local-dir models
./build/metal-server/bin/nemo-speech transcribe \
test_files/asr/wav/test/jfk.wav \
--model models/nemotron-speech-streaming-en-0.6b.q8_0.gguf \
--device metal
本文下載到的英文 Q8 是 699,872,960 bytes,SHA-256 為 d9a01898…3812d。11 秒 JFK WAV 在本文機器上辨識完整;先用 bundled sample 通過 smoke test,再換自己的錄音,才知道錯誤出在安裝、模型,還是音訊格式。授權也要分開看:runtime code 是 Apache-2.0;英文 ASR、Magpie、NanoCodec 權重採 NVIDIA Open Model License,Nemotron 3.5 multilingual 則採 OpenMDW 1.1。
要測中文,另下載 Nemotron 3.5 ASR,不要拿 English-only quick-start 硬跑:
hf download nvidia/nemotron-3.5-asr-streaming-0.6b \
nemotron-3.5-asr-streaming-0.6b.q8_0.gguf \
--revision 1c8deaecc64b91f034d73e08dd8b64625eb3395d \
--local-dir models
./build/metal-server/bin/nemo-speech transcribe chinese.wav \
--model models/nemotron-3.5-asr-streaming-0.6b.q8_0.gguf \
--language zh-CN --device metal

實作二:把 Magpie+NanoCodec 變成本機 TTS
官方 TTS 路線下載約 1.86 GiB,解壓後還要額外磁碟空間。本文固定 Hugging Face commit 452ef560…;該 commit 同時含 Magpie v2602 F16 GGUF 與無版本後綴的 .nemo archive,因此仍以實際 CLI/SDK smoke test 確認它和 NanoCodec F16 decoder 的配對。
hf download nvidia/magpie_tts_multilingual_357m \
--include magpie_tts_multilingual_357m.v2602.f16.gguf \
--include magpie_tts_multilingual_357m.nemo \
--revision 452ef560f972c38d5fc16476259aac9456453547 \
--local-dir models/magpie-tts
mkdir -p models/magpie-tts/extracted
tar -xf models/magpie-tts/magpie_tts_multilingual_357m.nemo \
-C models/magpie-tts/extracted
hf download nvidia/nemo-nano-codec-22khz-1.89kbps-21.5fps \
nemo_nano_codec_22khz_1.89kbps_21.5fps.decoder.f16.gguf \
--revision fc00890b604aa2de298d2641ffc6c5f6caf8c4d7 \
--local-dir models/nano-codec
./build/metal-server/bin/nemo-speech synthesize \
"Hello from a local speech API." \
--magpie-model models/magpie-tts/magpie_tts_multilingual_357m.v2602.f16.gguf \
--codec-model models/nano-codec/nemo_nano_codec_22khz_1.89kbps_21.5fps.decoder.f16.gguf \
--tokenizer-dir models/magpie-tts/extracted \
--language en-US --voice John --device metal \
--seed 42 --force --output hello.wav
中文 TTS 先不要照抄這條。官方 TTS 模型文件說明,預設 build 的 runtime 語言清單不含 zh-CN;要先拉 Git LFS 資產、初始化 cppjieba,並用 NEMO_SPEECH_TTS_WITH_ZH=ON 重新 configure。Magpie 的 John、Sofia、Aria、Jason、Leo 是五個固定 speaker identity,不是 voice cloning,也不代表中文母語口音。
實作三:啟動本機 HTTP server 與 playground
./build/metal-server/bin/nemo-speech serve \
--asr-model models/nemotron-speech-streaming-en-0.6b.q8_0.gguf \
--tts-model models/magpie-tts/magpie_tts_multilingual_357m.v2602.f16.gguf \
--codec-model models/nano-codec/nemo_nano_codec_22khz_1.89kbps_21.5fps.decoder.f16.gguf \
--tokenizer-dir models/magpie-tts/extracted \
--host 127.0.0.1 --port 8080 --open
瀏覽器會開啟 playground。先看 Ready,再用 Transcribe 上傳 WAV、用 Synthesize 產生語音;也可查 curl http://127.0.0.1:8080/v1/models 確認實際 capabilities、languages 與 voices。不要只因按鈕存在就推定該 capability 已載入。

實作四:用 curl 與 OpenAI SDK 接入應用
先用 curl 驗證 server,再換 SDK;這就像先確認插座有電,才怪家電。ASR 目前只收 WAV upload:
curl --fail-with-body -sS http://127.0.0.1:8080/v1/audio/transcriptions \
-F file=@recording.wav \
-F model=default \
-F response_format=verbose_json
curl --fail-with-body -sS http://127.0.0.1:8080/v1/audio/speech \
-H 'Content-Type: application/json' \
-d '{"model":"default","voice":"John","input":"Hello locally","response_format":"wav"}' \
-o hello-api.wav
file hello-api.wav
前面建立的 .venv 已裝好 SDK;若你另開 shell,先重新執行 source .venv/bin/activate。Python SDK 只要把 base_url 指向本機;api_key 在未啟用 server key 時只是 SDK 要求的非空 placeholder。下面把轉錄文字直接做成一句回覆,跑完最短的「聽到 → 說回去」閉環:
from pathlib import Path
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8080/v1",
api_key="local",
)
with open("recording.wav", "rb") as audio:
result = client.audio.transcriptions.create(
model="default",
file=audio,
)
print(result.text)
reply = f"I heard: {result.text}"
speech = client.audio.speech.create(
model="default",
voice="John",
input=reply,
response_format="wav",
)
speech.write_to_file(Path("sdk-tts.wav"))
這裡的「相容」至少有這些邊界:request 的 model 不會切換模型;TTS 只接受 wav/pcm 且 speed 只能是 1.0;HTTP TTS 會等整段合成完才回傳,不是邊生成邊播放。傳入 OpenAI 的 alloy、nova 時,server 會回退本機預設 speaker,不是同名音色;請改用 /v1/models 實際列出的 speaker。
實作五:Realtime WebSocket 轉錄不是完整 Realtime API
連到 ws://127.0.0.1:8080/v1/realtime 後,server 先送 session.created;你可以在音訊開始前送一次 session.update,接著傳 little-endian mono PCM16 binary frame,最後用 input_audio_buffer.commit 收尾。完成事件的 transcript 就是最終文字。
import asyncio, json, wave, websockets
async def transcribe(path="recording.wav"):
with wave.open(path, "rb") as wav:
assert wav.getnchannels() == 1 and wav.getsampwidth() == 2
rate = wav.getframerate()
assert 8000 <= rate <= 96000
pcm = wav.readframes(wav.getnframes())
async with websockets.connect("ws://127.0.0.1:8080/v1/realtime") as ws:
print(await ws.recv()) # session.created
await ws.send(json.dumps({
"type": "session.update",
"session": {"sample_rate": rate, "language": "en-US"},
}))
chunk_bytes = int(rate * 2 * 0.16) # 160 ms PCM16
for offset in range(0, len(pcm), chunk_bytes):
await ws.send(pcm[offset:offset + chunk_bytes])
await asyncio.sleep(0.16) # 模擬麥克風原速送入
await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))
final = None
async for raw in ws:
event = json.loads(raw)
if event["type"].endswith("transcription.completed"):
final = event["transcript"]
elif event["type"] == "error":
raise RuntimeError(event["error"]["message"])
elif event["type"] == "input_audio_buffer.committed":
print(final or "")
break
asyncio.run(transcribe())
上例是「檔案模擬麥克風」的 protocol smoke test;真正 live mic 可直接用 playground。在此 commit,官方只把這條 socket 文件化為 live PCM16 transcription,列出的事件也集中在轉錄、buffer 與 error。把它當 NeMo-Speech.cpp 自己的即時轉錄協定,不要當成完整 OpenAI Realtime。
實測:英文、中文、10 dB 白噪音與 61 秒長音檔
以下不是跨模型排行榜,而是同一台 M4、同一個 commit、Q8 GGUF 的小樣本 case study。英文 WER 先轉小寫、移除標點再計算;中文先做 NFKC、把拉丁字母轉小寫,只保留 Unicode letters/numbers/CJK 並去除標點與空白,再按字元計算 CER。長音檔是 macOS Samantha 合成的 61.3 秒、163 個英文單字壓力測試;中文也是 macOS Tingting/Meijia 系統語音。兩者都用來做可重複的 pipeline 診斷,不代表真人口音與自然噪音。

最有感的是 warm HTTP:11 秒英文 WAV 在模型已載入後約 0.19 秒完成;用 seed 42 加入固定白噪音、把 SNR 設為 10 dB 後,版本仍保留全部單字。即時測試則刻意按音檔原速送 160 ms PCM chunk;第一個 partial 約 0.84 秒,commit 到 completed 約 0.11 秒。這兩個數字回答不同問題,不能混成一個「總延遲」。
中文結果更直接暴露語言邊界:17.3 秒 zh-CN 合成樣本的嚴格 CER 是 19.7%,warm HTTP 中位數約 0.44 秒;15.96 秒 zh-TW 系統語音仍以官方 zh-CN language code 辨識,script-sensitive CER 是 35.5%,中位數約 0.38 秒。評分沒有做簡繁轉換,阿拉伯數字與口說中文數字也算差異;錯誤集中在簡繁、近音字與 NeMo/OpenAI 等混合詞。官方模型卡把 zh-CN 放在 broad-coverage tier,因此真正要上線,應像做 AI evals 一樣用真人錄音按裝置、房間、口音與關鍵詞分桶。
要做完整語音助理,還要整合哪六塊?
- Wake word:用小模型低功耗等待「Hey…」,不要讓完整 ASR 24 小時硬跑。
- VAD/endpointing:NeMo-Speech.cpp 已有可選能力;VAD 需另載 Silero GGUF,endpointing 預設關閉,可用 token-silence 或搭配 VAD,最後仍要接上 turn-taking。它解決切句,不等於喚醒詞。
- LLM 與對話狀態:ASR 只把聲音變文字,誰來理解與決策仍是另一層。可先用 AI Agent Harness 建立整體心智模型。
- 播放佇列與 barge-in:使用者插話時,要停止舊音訊並取消正在生成的回覆。
- Echo control:避免麥克風把喇叭剛播出的 TTS 再送回 ASR,形成自己和自己對話。
- 安全與生命週期:處理權限、timeout、重連、log、模型載入與 shutdown;實作框架可接著看 如何打造 Agent Harness。
手機、Raspberry Pi、隱私與公開服務:四條安全邊界
- 手機跑模型 ≠ 手機當 client:截至本文查核日,官方安裝文件列 Linux/macOS/Windows 與 x86_64/aarch64 build target,但未提供 Raspberry Pi、Android 或 iOS 的裝置級效能驗證。手機用區網 HTTP 呼叫桌機,是另一件事。
- 瀏覽器麥克風需要 secure context:把 host 改成
0.0.0.0並不能保證手機瀏覽器可錄音;區網方案還要可信 HTTPS、權限與實機測試。 - 本機不等於整條資料流都不出機:在預設 loopback 下,音訊送到同一台電腦上的 process,不需上傳雲端 speech API;若文字接著送雲端 LLM,資料邊界也跟著改變。
- 不要裸露 port:預設沒有 API key、TLS build 也是關閉。教學維持
127.0.0.1;區網或外網服務要另加 key、TLS/可信 reverse proxy,並參考 AI Agent 金鑰與祕密管理。
常見問題 FAQ
1. NeMo-Speech.cpp 可以完全取代 OpenAI Audio API 嗎?
不能。它提供一個實用子集合:WAV transcription,以及 WAV/PCM TTS;格式、speed、voice、model selection 與完整 OpenAI 服務不同。
2. 官方英文 Q8 可以直接辨識中文嗎?
不應這樣用。官方 quick-start model 只支援英文;中文請換 Nemotron 3.5 multilingual,server 也要重新載入該模型,語言碼用 zh-CN。
3. 中文 TTS 是預設開箱即用嗎?
不是。預設 TTS build 不含 zh-CN;需 Git LFS、cppjieba 與 NEMO_SPEECH_TTS_WITH_ZH=ON custom build,而且五個 speaker 不是中文 voice cloning。
4. 我能用 OpenAI Python SDK 嗎?
可以,但只限相容子集。把 base_url 換成本機 /v1 即可;本文已實測 ASR 與指定 response_format="wav" 的英文 TTS。
5. Raspberry Pi 或手機可以即時跑嗎?
目前不能由官方 build target 直接推定。Linux aarch64 只是編譯目標;記憶體、backend、散熱與即時速度都要在指定裝置測。也別把社群的 Talk-to-Pi 名稱誤認成 Raspberry Pi 實測。
6. 它內建 wake word 嗎?
截至 2026 年 8 月 8 日,本文查核的官方 README、server 與 API 文件沒有文件化 wake-word stage。它們涵蓋手動/持續錄音、可選 VAD、endpointing 與 ASR;喚醒詞應另接專門 detector。
7. 本機跑就一定完全私密嗎?
要看整條管線。loopback ASR/TTS 可留在同一主機;區網麥克風、雲端 LLM、remote logging 或 analytics 都會改變資料邊界。
8. 這個 server 適合直接上 production 嗎?
不建議把預設設定直接公開。它適合 local use 與 direct integration;對外服務至少要啟用內建 API key 與 TLS build/可信 reverse proxy,並另做限流、觀測與容量測試。官方 supported production path 另指向 NVIDIA NIM。
給新手的 5 個重點
- 先用 bundled JFK WAV 通過 smoke test,再換自己的麥克風。
- 英文 ASR、中文 ASR、TTS 是不同 artifacts;別把一個 Q8 想成全家桶。
- 先用 curl 驗證 endpoint,再接 OpenAI SDK;
model只是 placeholder。 - 延遲分 cold load、warm request、first partial 與 commit-to-final,不能只報一個數字。
- 真正品質要用自己的口音、房間、關鍵詞、噪音與長音檔做 eval。
📚 延伸閱讀:把語音 API 接成真正應用
- 本機模型部署教學:熟悉下載權重、啟動 runtime 與驗證輸出的共同思路。
- AI Evals 完整解析:把「感覺好像很準」變成可重複測量的測試集。
- AI Agent Harness 是什麼:理解 LLM、工具與執行層如何接在一起。
- 實作 Agent Harness:把 ASR 文字送入決策層,再把回答交給 TTS。
- AlphaLab AI 專區:繼續建立模型、工具與開發的完整地圖。
- AlphaLab 課程:想把零散工具串成可運作產品,可從系統化實作路線開始。
結語:先做一個可靠的「按住說話」,再追求永遠在線
NeMo-Speech.cpp 最迷人的地方,不是它已經替你做好整個語音助理,而是它把「耳朵、嘴巴、插座」搬到同一台電腦,讓你可以量、可以換、也可以自己決定資料邊界。最好的第一步不是直接做 wake word 常駐監聽,而是今天先跑通一個按鈕:錄一段 WAV,看到文字,再讓同一個本機 server 說回一句話。
當這條最短閉環穩定後,再視需求載入 VAD、啟用/調校 endpointing,並接上 realtime、LLM 與 barge-in。你會很快發現:本機語音 API 是助理的感官,不是助理本身。把這個邊界守住,後面的每一次擴充都會更容易除錯,也更誠實。



