【语音识别部署】— sherpa-onnx:让 ASR 模型跑得更快、跑在任何地方

【语音识别部署】--- sherpa-onnx:让 ASR 模型跑得更快、跑在任何地方

上一篇 FunASR 实战 里,我们用 FunASR 跑通了完整的语音识别 pipeline。但有个问题:FunASR 依赖 PyTorch 运行时,模型加载慢、显存占用大、部署到边缘设备更是想都别想。

sherpa-onnx 解决的就是这个问题。它是 k2-fsa 项目组维护的推理加速库,基于 ONNX Runtime,支持把各种 ASR 模型(包括 FunASR 的 Paraformer、Whisper、Zipformer 等)导出成 ONNX 格式后高效推理。说直白点:FunASR 负责训练和实验,sherpa-onnx 负责部署和上线。


一、sherpa-onnx 的定位

sherpa-onnx 不是一个训练框架,它只做推理。它的核心卖点:

特性 说明
跨平台 Linux / macOS / Windows / Android / iOS / 树莓派 / WebAssembly
多语言绑定 Python / C++ / Go / Rust / Java / C# / Kotlin / Swift / Dart
不依赖 PyTorch 只需要 ONNX Runtime,包体小、启动快
支持多种模型 Paraformer / Whisper / Zipformer / NeMo / SenseVoice 等
流式 + 非流式 实时识别和离线识别都支持
VAD 集成 内置 Silero VAD,可以和任意 ASR 模型组合

二、安装

bash 复制代码
pip install sherpa-onnx

就这么简单。不需要装 CUDA 版的 PyTorch,不需要配 ModelScope。sherpa-onnx 自带 ONNX Runtime,CPU 推理开箱即用。

如果你需要 GPU 加速:

bash 复制代码
# CUDA 11.x
pip install sherpa-onnx-cuda11

# CUDA 12.x
pip install sherpa-onnx-cuda12

验证安装:

bash 复制代码
python -c "import sherpa_onnx; print(sherpa_onnx.__version__)"

三、模型下载

sherpa-onnx 的预训练模型托管在 GitHub Releases 上。以中文 Paraformer 为例:

bash 复制代码
# 非流式 Paraformer(中文)
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-paraformer-zh-2023-09-14.tar.bz2
tar xvf sherpa-onnx-paraformer-zh-2023-09-14.tar.bz2
rm sherpa-onnx-paraformer-zh-2023-09-14.tar.bz2

# 流式 Paraformer(中英双语)
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-paraformer-bilingual-zh-en.tar.bz2
tar xvf sherpa-onnx-streaming-paraformer-bilingual-zh-en.tar.bz2

# Whisper(英文)
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-whisper-base.en.tar.bz2
tar xvf sherpa-onnx-whisper-base.en.tar.bz2

# Silero VAD 模型
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx

模型目录里通常包含:model.onnx(或 encoder/decoder 分离的文件)、tokens.txt。这两个文件是必需的。


四、非流式识别:Paraformer

非流式(offline)识别适合处理已录制好的音频文件,一次性送入整段音频,输出完整文本。

python 复制代码
import wave
import numpy as np
import sherpa_onnx


def read_wave(path):
    """读取 16-bit 单声道 WAV 文件,返回 float32 采样点和采样率"""
    with wave.open(path) as f:
        assert f.getnchannels() == 1, "只支持单声道"
        assert f.getsampwidth() == 2, "只支持 16-bit"
        raw = f.readframes(f.getnframes())
        samples = np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0
        return samples, f.getframerate()


# 初始化识别器
recognizer = sherpa_onnx.OfflineRecognizer.from_paraformer(
    paraformer="./sherpa-onnx-paraformer-zh-2023-09-14/model.onnx",
    tokens="./sherpa-onnx-paraformer-zh-2023-09-14/tokens.txt",
    num_threads=4,
    provider="cpu",  # GPU 推理改成 "cuda"
)

# 读取音频
samples, sample_rate = read_wave("./test.wav")

# 创建流 → 送入音频 → 解码
stream = recognizer.create_stream()
stream.accept_waveform(sample_rate, samples)
recognizer.decode_stream(stream)

result = recognizer.get_result(stream)
print(f"识别结果:{result.text}")

批量解码

sherpa-onnx 支持同时解码多段音频,这在批量处理场景下很有用:

python 复制代码
# 创建多个流
s1 = recognizer.create_stream()
s1.accept_waveform(sample_rate, samples)

s2 = recognizer.create_stream()
s2.accept_waveform(sample_rate, samples2)

# 一次性解码
recognizer.decode_streams([s1, s2])

print(recognizer.get_result(s1).text)
print(recognizer.get_result(s2).text)

五、流式识别:实时转写

流式(streaming/online)识别适合实时场景------麦克风录音、直播字幕、会议实时转写。音频一小块一小块地喂进去,识别结果实时输出。

python 复制代码
import wave
import numpy as np
import sherpa_onnx


# 初始化流式识别器
recognizer = sherpa_onnx.OnlineRecognizer.from_paraformer(
    tokens="./sherpa-onnx-streaming-paraformer-bilingual-zh-en/tokens.txt",
    encoder="./sherpa-onnx-streaming-paraformer-bilingual-zh-en/encoder.int8.onnx",
    decoder="./sherpa-onnx-streaming-paraformer-bilingual-zh-en/decoder.int8.onnx",
    num_threads=2,
)


def read_wave(path):
    with wave.open(path) as f:
        raw = f.readframes(f.getnframes())
        samples = np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0
        return samples, f.getframerate()


samples, sample_rate = read_wave("./test.wav")
stream = recognizer.create_stream()

# 模拟实时:每次送 100ms 的音频
chunk_size = int(0.1 * sample_rate)
offset = 0

while offset < len(samples):
    chunk = samples[offset : offset + chunk_size]
    stream.accept_waveform(sample_rate, chunk)
    offset += chunk_size

    while recognizer.is_ready(stream):
        recognizer.decode_stream(stream)

    if recognizer.is_endpoint(stream):
        result = recognizer.get_result(stream)
        print(f"一句话结束:{result.text}")
        recognizer.reset(stream)

# 刷出剩余音频
tail_padding = np.zeros(int(0.66 * sample_rate), dtype=np.float32)
stream.accept_waveform(sample_rate, tail_padding)
stream.input_finished()
while recognizer.is_ready(stream):
    recognizer.decode_stream(stream)
print("最终结果:", recognizer.get_result(stream).text)

🔴 重点is_endpoint() 是端点检测,当模型判断一句话说完了(有足够长的静音),返回 True。这时候你可以输出当前结果并 reset 流,开始识别下一句话。


六、VAD + 非流式 ASR:处理长音频的正确姿势

处理长音频时,直接整段送入非流式模型可能会出问题(内存占用、模型最大输入长度限制)。正确做法是用 VAD 先切分,再逐段识别。

sherpa-onnx 内置了 Silero VAD,用起来很方便:

python 复制代码
import wave
import numpy as np
import sherpa_onnx


def read_wave(path):
    with wave.open(path) as f:
        raw = f.readframes(f.getnframes())
        samples = np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0
        return samples, f.getframerate()


# 初始化 VAD
vad_config = sherpa_onnx.VadModelConfig()
vad_config.silero_vad.model = "./silero_vad.onnx"
vad_config.silero_vad.threshold = 0.5
vad_config.silero_vad.min_silence_duration = 0.5  # 静音超过 0.5s 认为一句话结束
vad_config.silero_vad.min_speech_duration = 0.25
vad_config.sample_rate = 16000

vad = sherpa_onnx.VoiceActivityDetector(vad_config, buffer_size_in_seconds=30)

# 初始化 ASR
recognizer = sherpa_onnx.OfflineRecognizer.from_paraformer(
    paraformer="./sherpa-onnx-paraformer-zh-2023-09-14/model.onnx",
    tokens="./sherpa-onnx-paraformer-zh-2023-09-14/tokens.txt",
    num_threads=4,
)

# 读取音频
samples, sample_rate = read_wave("./long_meeting.wav")

# 送入 VAD
vad.accept_waveform(samples)

# 逐段识别
results = []
while not vad.is_empty():
    segment = vad.front()
    vad.pop()

    stream = recognizer.create_stream()
    stream.accept_waveform(sample_rate, segment.samples)
    recognizer.decode_stream(stream)

    text = recognizer.get_result(stream).text
    if text.strip():
        start_time = segment.start / sample_rate
        results.append((start_time, text))
        print(f"[{start_time:.1f}s] {text}")

这个模式在生产环境里非常实用------VAD 负责"什么时候有人说话",ASR 负责"说了什么",各司其职。


七、Whisper 模型支持

如果你之前用 OpenAI 的 Whisper,sherpa-onnx 也能跑 Whisper 的 ONNX 版本:

python 复制代码
recognizer = sherpa_onnx.OfflineRecognizer.from_whisper(
    encoder="./sherpa-onnx-whisper-base.en/base.en-encoder.int8.onnx",
    decoder="./sherpa-onnx-whisper-base.en/base.en-decoder.int8.onnx",
    tokens="./sherpa-onnx-whisper-base.en/base.en-tokens.txt",
    language="en",
    task="transcribe",
    num_threads=2,
    provider="cpu",
)

samples, sample_rate = read_wave("./test.wav")
stream = recognizer.create_stream()
stream.accept_waveform(sample_rate, samples)
recognizer.decode_stream(stream)
print(recognizer.get_result(stream).text)

int8 量化的 Whisper 模型在 CPU 上的速度比 PyTorch 原版快 3~5 倍,精度损失很小。


八、FunASR 模型导出到 sherpa-onnx

如果你用 FunASR 训练了自己的模型,想部署到 sherpa-onnx,流程是:

  1. 导出 ONNX:FunASR 支持直接导出 ONNX 格式
  2. 准备 tokens.txt:从模型配置中提取词表
  3. 用 sherpa-onnx 加载
python 复制代码
# 用 FunASR 导出 ONNX
from funasr import AutoModel

model = AutoModel(model="FunAudioLLM/Fun-ASR-Nano-2512",
                  trust_remote_code=True,
                  device="cpu")

# 导出
model.export(type="onnx", quantize=True)

导出后的文件可以直接用 sherpa-onnx 的 OfflineRecognizer.from_paraformer() 加载。


九、性能对比:FunASR vs sherpa-onnx

我们在同一台机器(Intel i7-12700H, 无 GPU)上对 10 分钟中文音频做了简单测试:

指标 FunASR (PyTorch) sherpa-onnx (ONNX)
模型加载时间 ~8s ~0.5s
推理耗时(10min 音频) ~45s ~18s
内存占用 ~2.5GB ~800MB
包体大小 ~2GB(含 PyTorch) ~200MB

sherpa-onnx 的优势在 CPU 场景下尤其明显。GPU 场景下差距会缩小,但模型加载速度和内存占用仍然有明显优势。

⚠️ 注意:以上数据仅供量级参考,不同硬件、不同音频内容会有差异。


十、常见问题排查

Q1:FileNotFoundError: model.onnx

模型文件路径不对。确认你解压后的目录结构,paraformertokens 参数都要指向正确的文件。

Q2:识别结果是空的

检查音频格式。sherpa-onnx 要求 16kHz、16-bit、单声道 WAV。如果你的音频是 MP3 或其他格式,先用 ffmpeg 转换:

bash 复制代码
ffmpeg -i input.mp3 -ar 16000 -ac 1 -sample_fmt s16 output.wav

Q3:流式识别延迟高

调小 chunk_size。默认 100ms 是个不错的平衡点,对延迟敏感的场景可以试 60ms。

Q4:VAD 切得太碎 / 漏切

调整 min_silence_durationthreshold。阈值越高越严格(更难检测到语音),静音时长越长越不容易切分。


十一、总结

sherpa-onnx 是 ASR 模型部署的事实标准方案。它的核心价值:

  1. 去掉 PyTorch 依赖:模型加载从 8 秒降到 0.5 秒,内存占用砍 2/3。
  2. 跨平台:同一套模型文件,Python/C++/Android/iOS 都能跑。
  3. VAD + ASR 组合:内置 Silero VAD,长音频处理不再是问题。
  4. FunASR 无缝衔接:FunASR 训练的模型可以直接导出 ONNX 给 sherpa-onnx 用。

选型建议:

  • 实验/调参阶段 → 用 FunASR,方便迭代
  • 部署/上线阶段 → 用 sherpa-onnx,性能好、依赖少
  • 实时场景 → sherpa-onnx 的流式识别 + VAD

验证清单

  • ⭐ sherpa-onnx 安装成功:python -c "import sherpa_onnx; print(sherpa_onnx.__version__)"
  • 模型下载完成:Paraformer / Whisper / VAD 模型文件就位
  • 非流式识别跑通:Paraformer 识别测试音频
  • 流式识别跑通:实时逐块送入音频并输出结果
  • VAD + ASR 组合跑通:长音频被正确切分并逐段识别
  • 音频格式确认:16kHz / 16-bit / 单声道 WAV

参考资源

相关推荐
TheRouter4 小时前
AI Agent 的Prompt Injection 防御实战:从EchoLeak 零点击外泄到6层防护栈(含可运行代码与对比表)
人工智能·ai·prompt
小仙女的小稀罕4 小时前
录音会议纪要整理教程
人工智能
l1t4 小时前
DeepSeek总结的PostgreSQL 在 AI 基础设施中日益增长的作用
人工智能·postgresql
逆境不可逃4 小时前
【与我学 ClaudeCode】规划与协调篇 之 Subagent:上下文隔离的极简子代理框架
人工智能
视***间4 小时前
全栈算力矩阵,全域智能赋能——视程空间六大产品系列,构建边缘智能完整生态
人工智能·机器人·智慧城市·边缘计算·ai算力·终端算力
real_haha4 小时前
我做了一个仅有 1.3 MB 的 macOS 原生 AI 助手:AskNow
人工智能·macos
名不经传的养虾人4 小时前
从0到1:企业级AI项目迭代日记 Vol.29|自然语言变工作流:Agent 自动拼装子图的实现路径
人工智能·agent·ai编程·工作流·ai创业·企业ai
云登指纹浏览器4 小时前
AI选品工具实战对比:Jungle Scout vs Helium 10 vs ChatGPT选品,2026跨境卖家选哪个?
人工智能·chatgpt·跨境电商
RSTJ_16254 小时前
PYTHON+AI LLM DAY FIFITY-ONE
开发语言·人工智能·python