跳到主要内容

创建日期:2026-10-13 | 最近更新:2026-10-13 事实核对基于 sherpa-onnx 1.13.8(sherpa-onnx-node 原生 addon;node v24.19.0、Linux x64)。模型体积、RTF、内存均为本机实测,脚本与完整日志留档于 source/sherpa-onnx-lab/。

sherpa-onnx 101(第 1 篇 · 选型):给「中英混说」的程序员挑模型

程序员说话的样子,和朗读课文完全不一样。真实的一句需求是这样的:

「你把那个 useEffect 的依赖数组补一下,然后跑一遍 Vitest。」

一句话里三种成分:中文语法、英文技术词、中间还有「的」这种被夹在英文里的助词。这类「中英混说」(code-switching)是选模型时最该先过的一关——大部分中文 ASR 会把 useEffect 听成「由斯艾菲克特」,大部分英文 ASR 根本进不了中文。

这篇就干一件事:用一个可复现的对照实验,把「选哪个模型」这件事从感觉变成数字。

0. 先给结论​

#结论证据
1挑模型先看是否支持中英混说,这一条筛掉的比什么都多。中文模型听不懂英文术语,英文模型听不懂中文§2
2「越新越准」成立,「越新越快」不成立:2026 年的 X-ASR int8 比 2023 年的 mobile 版准得多,但 RTF 反而更差(0.11 对 0.086)§3
3老的 2023 双语模型不是废的——它体积大 2.2 倍、速度还快 1.3 倍,只有在混说场景才明显掉队§3
4纯英文场景别上大模型:Moonshine v2 tiny 只要 43 MB,RTF 0.026(38 倍实时)§4
5模型体积和内存不是一回事:148 MB 的 encoder 加载后占 324 MB RSS,差不多是文件体积的 2 倍§5
6「对编程领域友好」这件事,模型本身给不了一半,另一半要自己补——热词和同音词替换才是听懂黑话的关键(第 3 篇)§6

1. 编程场景对语音模型的三条硬要求​

先把需求写清楚,不然选型就是凭感觉:

要求具体是什么不满足会怎样
中英混说一句话里中英文自由切换,且英文词是中英混训语料里的词术语被音译成汉字,代码没法用
低延迟 / 可流式边说边出字,用户说的时候屏幕上就有反馈只能"录完再转",交互退化成一个按钮
体积可控前端要能下载、浏览器内存要扛得住页面加载 400 MB,用户直接走人

这三条里,第一条是筛子,后两条是预算。

2. 模型家族速查:先分清「解码结构」​

sherpa-onnx 支持的模型很多,但按解码结构分只有几大类。理解结构比记名字有用——它决定了你能不能用某些特性(比如热词只支持 transducer)。

家族解码结构代表模型流式支持热词特点
Transducer(zipformer 等)encoder + decoder + joiner 三段式zipformer、X-ASR、Nemotron是是流式的标准答案,延迟可调(看 chunk 大小)
Paraformer非自回归 CTC-likeFunASR 的 paraformer是(有流式版)否中文很强、解码快,但定制手段少
CTC单塔 + CTC 头zipformer-ctc、NeMo CTC、Wenet CTC是否结构最简单,可配 HLG 图解码
Attention Encoder-Decoder编码器 + 自回归解码Whisper、Moonshine、Canary、FireRedASR否否多语种/鲁棒性强,但都只能整段跑
LLM-based ASR语音编码器 + 语言模型Qwen3-ASR、FunASR Nano否否2026 年的新方向,重但上限高

如果你要的是「实时字幕」或「语音输入」,transducer 基本是唯一选择——它是唯一同时具备「原生流式」+「可热词定制」的结构。这条结论后面第 2、3 篇都会用到。

一个次要但实用的判断:中英混说的模型,词表通常标记为 cjkchar+bpe(汉字按字、英文按子词)。这个信息在配置热词时是必填项,第 3 篇会踩到。

3. 主实验:两代中英双语模型对照​

拿两个都是「中英双语、流式、int8」的模型,跑同样四段音频:

X-ASR 480ms int8zipformer bilingual mobile
发布2026-06-052023-02-20
权重来源Gilgamesh-J/X-ASRicefall(k2-fsa 官方)
chunk 大小480 ms(decode_chunk_len_=48)320 ms(decode_chunk_len_=32)
模型目录体积163 MB(encoder 148 MB)360 MB
单文件 encoder148 MB—
加载后常驻 RSS324 MB253 MB

(两个模型的 release 资产名分别是 sherpa-onnx-x-asr-480ms-streaming-zipformer-transducer-zh-en-int8-2026-06-05 和 sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20-mobile。)

3.1 四段音频的逐句对照​

四段都是中英混说的真实口语(test_wavs/0..3.wav,取自模型包自带的测试集):

音频X-ASR 480ms int8(2026)bilingual mobile(2023)谁对
0.wav(10.05s)昨天是 MONDAY TODAY IS 礼拜二 THE DAY AFTER TOMORROW 是星期三昨天是 MONDAY TODAY IS LIBRAR THE DAY AFTER TOMORROW是星期三新
1.wav(5.10s)这是第一种第二种叫呃与 ALWAYS ALWAYS 什么意思啊这是第一种第二种叫呃与 ALWAYS ALWAYS什么意思啊平
2.wav(4.69s)就是平凡的啊不认识接下来 FREQUENTLY 频繁的这个是频繁的啊不认识记下来 FREQUENTLY频繁的各有胜负
3.wav(8.83s)第一句是个什么时态加了 YES 是一般现代时对我们把它时态写上去第一句是个什么时态加了 ES 是一般现在时对后面它时态写上新

差别最刺眼的是 0.wav:「礼拜二」被 2023 版听成了英文单词 "LIBRAR"。这恰好是前面说的那个失效模式——中文词被强行塞进英文词表,输出一个不存在的英文单词。2026 版没犯这个错。

2.wav 上则是 2023 版赢了("这个/记下来"对,"平凡/接下来"错),说明新版不是全面碾压,只是整体更稳。

3.2 速度:新款反而更慢​

音频X-ASR 480ms RTF2023 mobile RTF
0.wav0.1120.088
1.wav0.1300.099
2.wav0.1280.099
3.wav0.1160.090
均值0.1210.094

2023 版快约 1.3 倍。 两者都是 2 线程、都是 int8、都在同一次 run.sh 里跑完,decode_chunk_len_ 分别是 48 / 32(开 debug: 1 时模型初始化会打出来)。合理解释是每步要算的帧更多、encoder 也更大(148 MB)——但这是我从数字和模型结构做的推断,不是官方说明,当参考不当结论。

3.3 这条对照给出的选型意见​

  • 只在中英混说场景用 → 选 2026 的 X-ASR,约 29% 的速度损失换明显更低的术语错误率,值。
  • 纯中文或纯英文、且机器弱 → 2023 mobile 版性价比更高(快、内存也小一点)。
  • 别只看发布时间。这两个模型的差距不在"新旧",而在"测什么数据"。

4. 纯英文侧:小模型能有多小​

如果你的场景是纯英文(比如给英文播客做字幕),那 300 MB 的双语模型纯属浪费。Moonshine v2 tiny 量化版:

项值
模型目录43 MB(encoder_model.ort 12.7 MB + 合并后的 decoder)
加载后 RSS162 MB
音频3.845 s(24 kHz,会被自动重采样到 16 kHz)
识别结果Ask not what your country can do for you. Ask what you can do for your country.
5 次耗时0.109 / 0.106 / 0.104 / 0.103 / 0.100 s
RTF0.026(38 倍实时)

一句话总结:43 MB 换来 38 倍实时的英文识别。 这是"放浏览器里也敢用"的量级。

这里有个配置字段的坑值得单独说:

// ✅ Moonshine v2 用这个字段名
modelConfig: {
moonshine: { encoder: '...encoder_model.ort', mergedDecoder: '...decoder_model_merged.ort' },
}

// ❌ 这样写会直接报错
modelConfig: {
moonshine: { encoder: '...', uncachedDecoder: '...', cachedDecoder: '...' },
}

报错信息其实很明确:

offline-moonshine-model-config.cc:Validate:133 Please provide --moonshine-preprocessor for v1
or --moonshine-merged-decoder for v2
SherpaOnnxCreateOfflineRecognizer:698 Errors in config

但 npm 包里的 types.js 只声明了 v1 的字段名(preprocessor / encoder / uncachedDecoder / cachedDecoder),照着 JSDoc 补全会踩坑。不信你看官方仓库 master 上的 Node 示例,用的就是 mergedDecoder。

5. 预算表:体积 ≠ 内存​

把这一篇用到的模型摊平:

模型磁盘目录加载后 RSSRTF适合
Silero VAD0.6 MB74 MB0.0085所有链路的前置
KWS zipformer 3M(中英)39 MB93 MB0.029本地待机唤醒
Moonshine v2 tiny(英)43 MB162 MB0.026英文识别
X-ASR 480ms int8(中英)163 MB324 MB0.121中英流式识别
bilingual mobile(中英)360 MB253 MB0.094中英流式识别(省算力)

注意 RSS 常常接近文件体积的 1.5~2 倍(X-ASR:148 MB 的 encoder 撑起 324 MB 进程)。原因是 ONNX Runtime 会把权重加载进内存、还有运行时的工作区。做前端预算时按「文件体积 × 2」估比按文件体积估安全得多。

另外注意 360 MB 的模型 RSS 比 163 MB 的还小——因为前者目录里塞了 fp32 和 int8 两套权重,实际只加载了 int8 那套。按目录体积做预算会高估。真要精算,看单个 encoder 文件。

6. 模型给不了的另一半​

到这里有个必须说清楚的事:上面的模型没有一个认识 useEffect 或 Vitest。 我用本地 TTS 合成了一句真实需求("把 useEffect 的依赖数组补上,然后跑一遍 vitest"),丢给两个双语模型:

X-ASR 480ms → 八 USE EFFECT 依赖数组补上然后跑一遍 VIRUS
2023 mobile → 但 第一赖树族补上然后跑一遍 Y
  • useEffect 被听成两个英文单词 USE EFFECT(还算能忍);
  • vitest 被听成 VIRUS(病毒)——这是最典型的失效:发音相近的常见词胜出,专业词被挤掉。

这不是模型选得不对,而是通用语音模型的天花板:训练语料里 "virus" 的出现频率比 "vitest" 高几个数量级。要解决它必须在解码阶段给模型提供上下文——也就是热词(contextual biasing)和同音词替换。

这就是第 3 篇的全部内容。先剧透一张表,它是在同一个 2023 mobile 模型上跑出来的:

模式结果
greedy_search昨天是 MONDAY TODAY IS LIBRAR THE DAY AFTER TOMORROW是星期三
modified_beam_search昨天是 MONDAY TODAY IS LIVE AFTER TOMORROW是星期三
modified_beam_search + 热词「礼拜二」昨天是 MONDAY TODAY IS礼拜二 THE DAY AFTER TOMORROW是星期三

一份两行的词表,把一句句法和语义都错了的话整个拉回来了。

7. 选型决策树​

按顺序问下来就行:

1. 只要能听懂中文?
├─ 是 → 中文 zipformer / paraformer(体积小、中文准)
└─ 否 ↓

2. 中英混说,还是纯英文?
├─ 纯英文 → Moonshine v2 tiny(43 MB / RTF 0.026)够用就不要更大的
└─ 中英混说 ↓

3. 要流式吗?
├─ 要 → transducer 类,且必须是中英双语版本
│ ├─ 术语错误率优先 → X-ASR 480ms int8(2026)
│ └─ 速度/内存优先 → bilingual mobile(2023)
└─ 不要 → 可以上 Whisper / SenseVoice / FireRedASR 这类更重的模型

4. 术语还是不对?
→ 加热词(第 3 篇);中文术语还可以加同音词替换

下一篇把这条链路真正搭起来:流式怎么喂、长音频怎么切、端点检测为什么会让你的最后一句凭空消失。

复现命令:cd source/sherpa-onnx-lab && bash download-models.sh && bash run.sh | tee run.log 第 1、4 节的数据在 run.log 的「02 非流式 ASR」与「04 流式 ASR」两段;第 6 节的 TTS 对照在「05b」段。