sherpa-onnx Moonshine v2 模型集成指南:tokens.txt 生成、模型下载与 ONNX 推理全流程
【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx
导读
本文围绕 scripts/moonshine/v2 目录下的整套 Moonshine v2 模型处理脚本展开,完整讲解 sherpa-onnx 项目中 Moonshine v2 从「原始权重」到「可离线部署的 sherpa-onnx 模型包」的转换链路,包括tokenizer.bin到tokens.txt的转换原理、多语言量化模型的下载与打包流程、encoder/decoder 的 ONNX 推理细节,以及面向 QNN 平台的固定形状导出方案。读完本文,你将能独立复现 Moonshine v2 模型的转换、测试与部署流程,并理解 sherpa-onnx C++ 离线识别器加载 Moonshine v2 模型所需的文件与参数。
Moonshine v2 与 v1 的模型结构差异
sherpa-onnx 的 Moonshine 集成分为 v1 与 v2 两套脚本目录:
- scripts/moonshine(v1):使用
preprocess.onnx、encode.onnx、uncached_decode.onnx、cached_decode.onnx四个文件,并依赖 Hugging Face 上的tokenizer.json; - scripts/moonshine/v2(v2):采用合并后的两文件结构,只保留encoder + merged decoder,文件格式可以是 ONNX 或 ORT 量化格式:
encoder_model.onnx + decoder_model_merged.onnx encoder_model.ort + decoder_model_merged.ortv2 脚本目录中共有四类资源,形成完整的「下载 → 转换 → 验证 → 部署」闭环:
| 文件 | 作用 |
|---|---|
| run.sh | 一键下载多语言 Moonshine 模型并打包为 sherpa-onnx 发布格式 |
| generate_tokens.py | 从tokenizer.bin生成 sherpa-onnx 所需的tokens.txt |
| test.py | 用 onnxruntime 纯 CPU 端到端验证 encoder/decoder 推理 |
| qnn/export_onnx.py 与 qnn/test_onnx.py | 面向高通 QNN 的固定形状导出与验证 |
注意:v2 模型必须用 generate_tokens.py 从tokenizer.bin生成tokens.txt,而不能直接沿用 v1 的tokenizer.json,因为 v2 的 tokenizer 以二进制字节流形式分发(对应 moonshine-ai/moonshine 的 PR #73 中引入的新 tokenizer 格式)。
tokenizer.bin 二进制格式与 tokens.txt 生成
为什么需要转换
sherpa-onnx 的离线识别器统一通过文本格式的tokens.txt(每行token_base64 词元id)来加载词表,而 Moonshine v2 分发的词表是二进制文件tokenizer.bin,因此需要一个转换脚本。
转换脚本实现
generate_tokens.py 的核心逻辑非常简洁:它复用 test.py 中定义的BinTokenizer解析tokenizer.bin,随后将每个词元的原始字节做 base64 编码并写入tokens.txt:
tokenizer = BinTokenizer("./tokenizer.bin") with open("./tokens.txt", "w", encoding="utf-8") as f: for idx, token_bytes in enumerate(tokenizer.tokens): b64 = base64.b64encode(token_bytes).decode("ascii") f.write(f"{b64} {idx}\n")关键点在于直接对字节做 base64 编码而非 UTF-8 解码:Moonshine 的词表包含大量多字节 UTF-8 片段,若按字符串解码会损坏字节边界,base64 可无损承载任意二进制 token。
BinTokenizer 的变长编码解析
test.py 中的BinTokenizer._load实现了 tokenizer.bin 的自定义变长编码格式:
if first == 0: tokens.append(b"") # 空 token elif first < 128: length = first # 单字节长度 else: second = data[i]; i += 1 length = (second * 128) + (first - 128) # 两字节长度 token_bytes = data[i : i + length]其解码规则为:
- 首字节为 0 → 空 token(长度 0);
- 首字节 < 128 → 长度即首字节本身(最长 127 字节);
- 首字节 ≥ 128 → 长度 = 第二个字节 × 128 + (首字节 − 128),支持更长词元。
BinTokenizer.decode则先按 id 拼接原始字节流,再统一按 UTF-8 解码,并将 SentencePiece 风格的空格符▁替换为普通空格:
byte_stream = b"".join(self.tokens[i] for i in ids if i < len(self.tokens)) text = byte_stream.decode("utf-8", errors="replace") return text.replace("▁", " ").strip()多语言模型下载与发布包打包:run.sh 全解
run.sh 演示了从 Moonshine 官方仓库拉取各语言模型并转换为 sherpa-onnx 发布格式的完整流水线,主要步骤:
- 下载许可证:通过
wget获取 Moonshine 的 LICENSE,随模型包分发; - 设置模型缓存:
export MOONSHINE_VOICE_CACHE=$d将models目录指定为 moonshine_voice 下载工具的缓存目录; - 下载多语言模型:调用
python3 -m moonshine_voice.download,--language指定语言,--model-arch指定模型架构:
| 语言 | model-arch | 说明 |
|---|---|---|
| zh / ar / es / vi / uk | 1 | base 架构量化模型 |
| en / ja | 0 与 1 | tiny 与 base 两档均下载 |
| ko | 0 | tiny 架构 |
脚本注释中给出了 base 架构量化模型的典型体积(以base-ar、base-zh为例:decoder_model_merged.ort约 105M,encoder_model.ort约 30M,tokenizer.bin约 245K;tiny-en则仅约 30M + 13M),这些体积数据可帮助你评估目标平台的存储与内存预算;
- 逐模型打包:对
base-ar、base-en、base-es、base-ja、base-uk、base-vi、base-zh、tiny-en、tiny-ja、tiny-ko这 10 个模型依次执行:
mv models/download.moonshine.ai/model/$name/quantized/$name/* . python3 ./generate_tokens.py # tokenizer.bin -> tokens.txt rm tokenizer.bin d=sherpa-onnx-moonshine-$name-quantized-2026-02-27 mkdir -p $d mv *ort $d/ # encoder_model.ort + decoder_model_merged.ort cp LICENSE $d mv tokens.txt $d # 词表 curl -SL -O .../$lang.wav # 下载对应语言的测试音频 mv $lang.wav $d/test_wavs/0.wav tar cjfv $d.tar.bz2 $d # 压缩为发布包最终每个模型产出形如sherpa-onnx-moonshine-base-zh-quantized-2026-02-27.tar.bz2的发布包,内含encoder_model.ort、decoder_model_merged.ort、tokens.txt、LICENSE与test_wavs/0.wav,与 sherpa-onnx 其他 ASR 模型包的目录规范完全一致,可直接被 python-api-examples/offline-moonshine-decode-files.py 等示例加载。
纯 CPU 端到端推理验证:test.py
test.py 提供了一个不依赖 sherpa-onnx 本体、只用onnxruntime + librosa + numpy的推理验证脚本,用于在转换后第一时间确认模型可用性。
会话初始化与输入探测
OnnxModel.__init__创建 encoder/decoder 两个InferenceSession(均为 CPU provider,inter_op_num_threads=1),然后自动探测解码器输入来判断模型变体:
- 若存在名为
key_values的输入,从中解析num_head = n.shape[1]与head_dim = n.shape[3]; - 若存在
encoder_attention_mask输入,说明该模型需要 decoder attention mask,其num_layers = (输入总数 − 4) / 4;否则为(输入总数 − 3) / 4。
解码循环
main中的贪心解码流程与推理引擎的逐 token 生成逻辑一致:
encoder_out = model.run_encoder(samples) # 一次前向得到 last_hidden_state states = model.get_decoder_init_states() # 初始化 KV cache(每层 4 个张量) token_id = model.bos # bos=1 for step in range(max_len): # max_len = 时长秒数 × 15 logits, states = model.run_decoder(token_id, encoder_out, states) token_id = int(np.argmax(logits[0, 0])) # 贪心取最大概率 if token_id == model.eos: # eos=2 break tokens.append(token_id)其中run_encoder对音频做audio[None, :]增维并限制最长 8 秒(audio[: 8 * 16000]);run_decoder的输入布局分为「有 encoder_attention_mask」与「无」两种,最后一个输入均为use_cache_branch布尔标志(token_id != bos时为 True),并且只有token_id == bos时才整体刷新 KV cache 状态,否则仅更新 decoder 侧的 key/value——这正是「merged decoder」缓存复用机制的体现。
面向 QNN 的固定形状导出:qnn/export_onnx.py
scripts/moonshine/v2/qnn 提供了面向高通 QNN(Qualcomm Neural Network)平台的专门导出方案,与通用 ONNX 导出(动态形状)的关键差异在于全部使用固定形状,以满足 QNN 转换器的约束。
导出架构设计
导出脚本将模型拆成两个子图:
- Encoder(
AudioEncoderTensorCache):不仅计算音频编码,还顺带在解码器每一层上运行encoder_attn.k_proj / v_proj,直接输出每层的cross K/V cache(cross_k_i、cross_v_i),避免解码阶段重复计算; - Decoder(
TextDecoderTensorCache):采用split attention 模式——把自注意力拆成q@k_cache(历史部分)与q@k_new(当前 token 增量部分)分别计算再拼接 softmax,输出的this_self_k_i / this_self_v_i为增量 K/V,由调用方在外部更新缓存。
这种「encoder 出 cross K/V、decoder 出 delta K/V、缓存外部维护」的结构,与 sherpa-onnx/csrc/qnn/offline-moonshine-model-qnn.cc 中的 QNN 推理实现相互印证。
元数据与精度处理
导出时会在 ONNX 文件中写入自定义元数据(add_meta_data),包括num_decoder_layers、hidden_size、num_attention_heads、num_key_value_heads、head_dim、vocab_size、partial_rotary_factor、sampling_rate=16000、max_seq_len、max_audio_len、enc_seq_len等,供推理端直接读取而无需硬编码配置。此外还做了三类 QNN 兼容处理:
- LayerNorm 补零 bias:
patch_layernorm_bias为无 bias 的 LayerNorm 补上全零参数; - 缩放因子后移:将注意力 scale 移到矩阵乘之后(
scale_sq = scale * scale),避免产生 QNN 不友好的中间张量; - onnxsim 简化:导出后尝试用
onnxsim.simplify化简以规避 QNN converter 问题。
验证与量化数据准备
test_onnx.py 从模型元数据读取配置,加载音频后按固定长度补零(max_audio_len),逐 token 运行 decoder 并把 delta K/V 写回外部缓存;同时它会输出{name}-audio.raw与{name}-encoder.txt,为后续 QNN 量化收集校准数据。
sherpa-onnx 离线识别器中的 Moonshine v2 配置
Moonshine v2 模型在 sherpa-onnx 中通过离线识别器(OfflineRecognizer)加载,相关配置参数定义于 sherpa-onnx/csrc/offline-moonshine-model-config.cc:
| 参数 | 说明 |
|---|---|
--moonshine-encoder | encoder 模型路径,v1/v2 通用(v2 即encoder_model.onnx或encoder_model.ort) |
--moonshine-merged-decoder | v2 独有,合并后的解码器路径(decoder_model_merged.onnx或.ort) |
从源码可见校验逻辑:v2 路径要求 encoder 与 merged_decoder 均存在,且两个文件缺一不可。加载实现位于 sherpa-onnx/csrc/offline-moonshine-model-v2.cc,构造函数分别创建 encoder 与 decoder 两个Ort::Session;ForwardEncoder中还会根据 encoder 输入数量决定是否补一个全 1 的 int64 mask 张量(对应 test.py 中len(self.encoder.get_inputs()) > 1的分支),说明不同来源的 v2 编码器存在「带/不带 mask 输入」两种签名,脚本与 C++ 端都做了兼容处理。
Python 侧可直接参考 python-api-examples/offline-moonshine-decode-files.py 了解完整的OfflineRecognizer配置方式,C++ 侧可参考 cxx-api-examples/moonshine-v2-cxx-api.cc 与 c-api-examples/moonshine-v2-c-api.c。
与 v1 脚本的关系与迁移提示
v1 目录(scripts/moonshine)的 run.sh 与 export-onnx.py 走的是「四文件 +tokenizer.json」路线,并通过onnxruntime.quantization.quantize_dynamic生成*.int8.onnx(脚本注释特别提醒:preprocessor 不要用 int8,其对精度影响更大)。v2 则在源头直接下载官方量化好的.ort文件,省去本地量化环节,同时用合并 decoder 减少了部署文件数量。
选用建议:
- 若面向高通 QNN 等 NPU 平台,优先使用 scripts/moonshine/v2/qnn 的固定形状导出方案;
- 若仅做 CPU 离线识别,直接使用 v2 的
encoder_model.ort + decoder_model_merged.ort量化包即可获得更小的体积与更少的文件; - 无论哪种方案,
tokens.txt都必须由对应版本的 tokenizer 生成,v2 模型切勿复用 v1 的tokenizer.json转换结果。
总结
Moonshine v2 在 sherpa-onnx 中的落地链路清晰完整:通过 run.sh 下载多语言量化模型 → 用 generate_tokens.py 将tokenizer.bin转换为 base64 编码的tokens.txt→ 用 test.py 在纯 CPU 环境验证 encoder/decoder 贪心解码 → 最终以--moonshine-encoder与--moonshine-merged-decoder两个参数接入离线识别器;如需部署到 QNN 平台,则使用 qnn/export_onnx.py 导出固定形状、带完整元数据的 ONNX 模型。这套脚本不仅服务于 sherpa-onnx 的模型发布,也是一份可直接复用的「Moonshine v2 ONNX 化与验证」参考实现。
【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考