跳到主要內容

【2026 最新】NeMo-Speech.cpp 教學:把 NVIDIA ASR+TTS 變成本機 OpenAI 語音 API

最後更新: ·
NeMo-Speech.cpp 本機 ASR+TTS 語音 API 教學首圖

你想做一個會聽、會說的本機應用,第一個直覺可能是:錄音先送雲端 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 ReleasesTags 都是空的,沒有 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、TTS 與 HTTP API 架構圖
NeMo-Speech.cpp 提供「耳朵、嘴巴、插座」,但 wake word、LLM 與播放控制仍屬應用層。

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-servercuda-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
NeMo-Speech.cpp ASR Q8 與 TTS F16 模型檔案大小比較
ASR quick start 是一個 Q8;TTS 則要兩個 F16 GGUF 加 tokenizer archive。

實作二:把 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 已載入。

NeMo-Speech.cpp 本機 browser playground 實際畫面
本文實際啟動的本機 playground;capability 以 server 載入模型後的回傳為準。

實作四:用 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 只接受 wavpcmspeed 只能是 1.0;HTTP TTS 會等整段合成完才回傳,不是邊生成邊播放。傳入 OpenAI 的 alloynova 時,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 診斷,不代表真人口音與自然噪音。

NeMo-Speech.cpp 在 Apple M4 的 ASR、TTS、中文 CER 與 WebSocket 延遲實測
同一台 Apple M4 的本文實測;結果只代表這些樣本、模型與 backend。

最有感的是 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 一樣用真人錄音按裝置、房間、口音與關鍵詞分桶。

要做完整語音助理,還要整合哪六塊?

  1. Wake word:用小模型低功耗等待「Hey…」,不要讓完整 ASR 24 小時硬跑。
  2. VAD/endpointing:NeMo-Speech.cpp 已有可選能力;VAD 需另載 Silero GGUF,endpointing 預設關閉,可用 token-silence 或搭配 VAD,最後仍要接上 turn-taking。它解決切句,不等於喚醒詞。
  3. LLM 與對話狀態:ASR 只把聲音變文字,誰來理解與決策仍是另一層。可先用 AI Agent Harness 建立整體心智模型。
  4. 播放佇列與 barge-in:使用者插話時,要停止舊音訊並取消正在生成的回覆。
  5. Echo control:避免麥克風把喇叭剛播出的 TTS 再送回 ASR,形成自己和自己對話。
  6. 安全與生命週期:處理權限、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、serverAPI 文件沒有文件化 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 個重點

  1. 先用 bundled JFK WAV 通過 smoke test,再換自己的麥克風。
  2. 英文 ASR、中文 ASR、TTS 是不同 artifacts;別把一個 Q8 想成全家桶。
  3. 先用 curl 驗證 endpoint,再接 OpenAI SDK;model 只是 placeholder。
  4. 延遲分 cold load、warm request、first partial 與 commit-to-final,不能只報一個數字。
  5. 真正品質要用自己的口音、房間、關鍵詞、噪音與長音檔做 eval。

📚 延伸閱讀:把語音 API 接成真正應用

結語:先做一個可靠的「按住說話」,再追求永遠在線

NeMo-Speech.cpp 最迷人的地方,不是它已經替你做好整個語音助理,而是它把「耳朵、嘴巴、插座」搬到同一台電腦,讓你可以量、可以換、也可以自己決定資料邊界。最好的第一步不是直接做 wake word 常駐監聽,而是今天先跑通一個按鈕:錄一段 WAV,看到文字,再讓同一個本機 server 說回一句話。

當這條最短閉環穩定後,再視需求載入 VAD、啟用/調校 endpointing,並接上 realtime、LLM 與 barge-in。你會很快發現:本機語音 API 是助理的感官,不是助理本身。把這個邊界守住,後面的每一次擴充都會更容易除錯,也更誠實。

AlphaLab 精選

接著閱讀

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週一封,第一時間收到新文章與投資觀察。

我們不會 spam,隨時可退訂。