sherpa-onnx 模型接入实战:NVIDIA Parakeet Unified EN 0.6B 从 NeMo 到 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/nemo/parakeet-unified-en-0.6b 目录展开,完整讲解如何将 NVIDIA 开源的 Parakeet Unified EN 0.6B(一个支持统一流式/非流式的英文语音识别模型,内部为EncDecRNNTBPEModel)从 NeMo 格式导出为 sherpa-onnx 可加载的 encoder/decoder/joiner 三段式 ONNX 模型。读完本文你将掌握:该模型的网络结构与关键超参数、ONNX 各子模型的输入输出张量约定、int8 动态量化流程、以及基于 buffered streaming 机制的低延迟流式导出方案与仓库内的验证方法。
一、模型与导出目录概览
parakeet-unified-en-0.6b是 sherpa-onnx 的 NeMo 系列模型导出脚本之一(同目录下还包含 canary、parakeet-tdt-0.6b-v2、GigaAM 等)。该目录提供了两条导出链路:
- 非流式:
export_onnx.py+ run.sh; - 缓冲流式:
export_onnx_streaming.py+ run-streaming.sh。
两条链路都包含配套的 ONNX Runtime 冒烟测试脚本(test_onnx.py/test_onnx_streaming.py),用于验证导出结果并计算 RTF(实时率)。notes.md 正是这两次导出实验的记录档案:它依次给出了 PyTorch 模型结构(asr_model)、完整训练/推理配置(asr_model.cfg)、三个 ONNX 子图的输入输出张量签名、int8 量化后的文件体积以及实测推理结果。
二、模型结构拆解:EncDecRNNTBPEModel 的四段式架构
notes.md 中记录的asr_model结构属于 NeMo 的EncDecRNNTBPEModel(BPE 子词 + RNN-Transducer)。整个模型由四大部分组成:前端预处理器、Conformer 编码器、RNNT 预测网络(decoder)与联合网络(joint)。
2.1 预处理器:AudioToMelSpectrogramPreprocessor
(preprocessor): AudioToMelSpectrogramPreprocessor( (featurizer): FilterbankFeatures() )负责将 16 kHz 原始波形转为 128 维 FBank 特征。对应的配置(详见下文asr_model.cfg):窗长 25 ms、帧移 10 ms、hann 窗、n_fft=512、normalize='per_feature'。注意该预处理器在导出 ONNX 时不包含在模型中——特征提取由 sherpa-onnx 侧的 kaldi-native-fbank 完成(见test_onnx.py中的create_fbank(),其参数window_type="hann"、num_bins=128、is_librosa=True与 NeMo 配置一一对应)。
2.2 编码器:24 层 ConformerEncoder
编码器是模型的体积与算力主体,由三部分串联:
1) ConvSubsampling(时间维 8 倍下采样)
ConvSubsampling( (out): Linear(in_features=4096, out_features=1024, bias=True) (conv): MaskedConvSequential( (0): Conv2d(1, 256, (3,3), stride=(2,2), padding=(1,1)) (1): ReLU (2): Conv2d(256, 256, (3,3), stride=(2,2), groups=256) # depthwise (3): Conv2d(256, 256, (1,1)) (4): ReLU (5): Conv2d(256, 256, (3,3), stride=(2,2), groups=256) # depthwise (6): Conv2d(256, 256, (1,1)) (7): ReLU ) )采用dw_striding下采样方式:三次 stride=2 卷积将帧率降为 1/8(即subsampling_factor=8,这也是 10 ms 帧移对应 80 ms 编码帧间隔的来源),depthwise + pointwise 的组合控制参数量。
2) RelPositionalEncoding(相对位置编码)
用于配合 `self_attention_model='rel_pos'' 的相对位置自注意力,支持最长 5000 帧位置,dropout 0.1。
3) 24 个 ConformerLayer(d_model=1024)
每个层按顺序包含:
feed_forward1/feed_forward2:Linear(1024→4096) → Swish → Dropout(0.1) → Linear(4096→1024),即 FFN 扩展因子 4;conv:pointwise_conv1 (1024→2048) → CausalConv1D(1024, kernel=9, groups=1024) → BatchNorm1d → Swish → pointwise_conv2 (1024→1024),卷积核 9、组卷积,conv_norm_type='batch_norm';self_attn:RelPositionMultiHeadAttention,8 头,Q/K/V/out 均为 1024 维,另有linear_pos位置映射;- 每层带两个 LayerNorm、残差与
Dropout(0.1)。
2.3 预测网络:RNNTDecoder(双层 LSTM)
RNNTDecoder( (prediction): ModuleDict( (embed): Embedding(1025, 640, padding_idx=1024) (dec_rnn): LSTMDropout( (lstm): LSTM(640, 640, num_layers=2, dropout=0.2) ) ) )词嵌入表 1025 项(1024 个 BPE 子词 + 1 个 blank),隐层 640 维(pred_hidden=640),双层 LSTM。从源码结构看,这是标准 RNNT 预测网络:逐帧输入已预测 token,输出 640 维预测表示,与编码器输出在 joint 中融合。
2.4 联合网络:RNNTJoint
RNNTJoint( (pred): Linear(640→640) (enc): Linear(1024→640) (joint_net): Sequential(ReLU → Dropout(0.2) → Linear(640→1025)) )先分别将编码器表示(1024 维)与预测网络表示(640 维)映射到 640 维公共空间并相加,再经 ReLU 与线性层输出 1025 维 logits(1024 个 token + blank),用于贪婪搜索或 beam 搜索解码。
此外训练期还包含RNNTLoss、SpectrogramAugmentation(SpecAugment)与WER计算模块,这些在推理 ONNX 中均不需要。
三、核心配置 asr_model.cfg 逐段解读
notes.md 完整保存了导出时的模型配置(nemo_version: 2.7.0rc0),这是复现训练超参、理解推理前处理的关键一手资料。
3.1 全局与数据
{'sample_rate': 16000, 'rnnt_reduction': 'mean_volume', 'skip_nan_grad': False, 'model_defaults': {'enc_hidden': 1024, 'pred_hidden': 640, 'joint_hidden': 640}, 'train_ds': {'sample_rate': 16000, 'batch_size': 16, 'num_workers': 8, 'max_duration': 40.0, 'min_duration': 0.1, 'text_field': 'answer', 'use_bucketing': True, 'use_lhotse': True}, 'validation_ds': {}}- 采样率统一 16 kHz;训练数据最长 40 s、最短 0.1 s;
- 三个隐藏维度的统一入口:编码器 1024、预测网络 640、联合网络 640;
- 导出脚本(
export_onnx.py第 58 行)会把validation_ds置空,避免导出时加载验证集。
3.2 前端与增强
'preprocessor': {'_target_': 'AudioToMelSpectrogramPreprocessor', 'sample_rate': 16000, 'normalize': 'per_feature', 'window_size': 0.025, 'window_stride': 0.01, 'window': 'hann', 'features': 128, 'n_fft': 512, 'dither': 1e-05}, 'spec_augment': {'freq_masks': 2, 'time_masks': 10, 'freq_width': 27, 'time_width': 0.05}推理前处理要点:normalize='per_feature'意味着每段音频的 FBank 特征要按帧维做均值/方差归一化。在test_onnx.py(第 239–245 行)与buffered_streaming_helpers.py的normalize_per_feature()中都有完全一致的实现:
mean = features.mean(axis=0, keepdims=True) std = features.std(axis=0, keepdims=True) + 1e-5 # 加 1e-5 防除零 features = (features - mean) / std3.3 编码器配置
'encoder': {'n_layers': 24, 'd_model': 1024, 'subsampling': 'dw_striding', 'subsampling_factor': 8, 'subsampling_conv_channels': 256, 'ff_expansion_factor': 4, 'self_attention_model': 'rel_pos', 'n_heads': 8, 'att_context_size': [-1, -1], 'att_chunk_context_size': [[70], [1, 2, 7, 13], [0, 1, 2, 3, 4, 7, 13]], 'att_context_style': 'chunked_limited_with_rc', 'conv_kernel_size': 9, 'conv_norm_type': 'batch_norm', 'dropout': 0.1, 'pos_emb_max_len': 5000}这里的关键是att_context_size=[-1, -1](全上下文,即非流式)。注意att_chunk_context_size中已经内置了三档流式 chunk 方案(对应[[70], [1,2,7,13], [0,1,2,3,4,7,13]]),这正是 Parakeet Unified 系列"统一"能力的体现:同一套权重既能全上下文推理,也能通过设置 chunk 上下文切到流式。流式导出正是利用这一机制,见第五节。
3.4 预测网络与联合网络
'decoder': {'prednet': {'pred_hidden': 640, 'pred_rnn_layers': 2, 'dropout': 0.2}, 'blank_as_pad': True, 'vocab_size': 1024}, 'joint': {'jointnet': {'joint_hidden': 640, 'activation': 'relu', 'dropout': 0.2, 'encoder_hidden': 1024, 'pred_hidden': 640}, 'num_classes': 1024}与 2.3/2.4 节结构一一对应。num_classes=1024再加上 blank 即 joint 输出的 1025 维。
3.5 解码、损失与优化器
'decoding': {'strategy': 'greedy_batch', 'greedy': {'max_symbols': 10}, 'beam': {'beam_size': 2, 'score_norm': True, 'tsd_max_sym_exp': 50, 'alsd_max_target_len': 2.0}}, 'loss': {'loss_name': 'default', 'offline_loss_weight': 0.3, 'streaming_loss_weight': 0.7}, 'optim': {'name': 'adamw', 'lr': 0.0001, 'betas': [0.9, 0.98], 'weight_decay': 0.001, 'sched': {'name': 'CosineAnnealing', 'warmup_steps': 3000, 'min_lr': 5e-06}}- 默认解码策略为 greedy_batch,每帧最多发射 10 个 token(
max_symbols=10),这一约束在 ONNX 推理循环(test_onnx.py第 252 行、test_onnx_streaming.py第 126 行)中被原样保留; - 损失函数同时加权了离线(0.3)与流式(0.7)训练目标,印证了"unified"训练方式;
- 优化器 AdamW,学习率 1e-4,CosineAnnealing 调度、3000 步 warmup、最小学习率 5e-6。
3.6 词表与 tokenizer
'tokenizer': {'type': 'bpe', 'model_path': 'nemo:c9e35cde64e14bdc87cf70d543842217_tokenizer.model', 'vocab_path': 'nemo:28f042954ba747e99209b8ca5a223ba3_vocab.txt'}BPE 词表共 1024 个子词单元。export_onnx.py(第 61–65 行)会遍历asr_model.joint.vocabulary生成 tokens.txt,并在末尾追加一行<blk> 1024作为 blank 符号——因此 ONNX 推理时 blank 的索引恒为vocab_size(1024)。词表以空格符▁标记词首(如▁the),解码后需将其替换为空格再 strip。
四、非流式 ONNX 导出与子图张量签名
4.1 导出流程
export_onnx.py 的流程为:
- 加载模型:优先
restore_from("./parakeet-unified-en-0.6b.nemo"),否则from_pretrained("nvidia/parakeet-unified-en-0.6b"); - 写出
tokens.txt(1024 子词 +<blk>); - 分别调用
encoder.export()、decoder.export()、joint.export(),得到三个 ONNX 文件; - 对三个子图分别执行 onnxruntime 动态量化(
quantize_dynamic),encoder 用QUInt8,decoder/joiner 用QInt8; - 向 encoder 模型写入自定义元数据(
add_meta_data),关键字段包括:model_type=EncDecRNNTBPEModel、normalize_type=per_feature、pred_rnn_layers=2、pred_hidden=640、subsampling_factor=8、feat_dim=128、vocab_size=1024、version=2、comment="This model contains only the non-streaming part"。这些字段是 sherpa-onnx 加载模型时的依据(在线模型实现 中通过SHERPA_ONNX_READ_META_DATA读取同类字段)。
由于 encoder 体量巨大(fp32 权重约 2.3 GB),add_meta_data对encoder.onnx使用外部权重存储(encoder.weights)以避免单个文件超限。
4.2 三个子图的输入输出签名(来自 notes.md)
encoder(输入 128 维特征)
输入: audio_signal float [dynamic, 128, dynamic] # 布局为 [B, C, T] length int64 [dynamic] 输出: outputs float [dynamic, 1024, dynamic] # [B, 1024, T_enc] encoded_lengths int64 [dynamic]decoder(预测网络)
输入: targets int32 [dynamic, dynamic] target_length int32 [dynamic] states.1 float [2, dynamic, 640] # 双层 LSTM 初始状态 onnx::Slice_3 float [2, 1, 640] 输出: outputs float [dynamic, 640, dynamic] prednet_lengths int32 [dynamic] states float [2, dynamic, 640] 162 float [2, dynamic, 640]joiner(联合网络)
输入: encoder_outputs float [dynamic, 1024, dynamic] decoder_outputs float [dynamic, 640, dynamic] 输出: outputs float [dynamic, dynamic, dynamic, 1025] # 末维 1025 = vocab + blank4.3 非流式推理主循环
test_onnx.py 给出了完整的贪婪搜索流程,可直接作为参考实现:
- 用 kaldi-native-fbank 提取 128 维 FBank(hann 窗、
is_librosa=True),并按per_feature归一化; - 在音频末尾追加 2 秒静音(
tail_padding,第 229 行),保证尾部 token 有足够上下文完成发射; - 初始化 decoder 状态为全零
[2, 1, 640],输入 blank 得到初始decoder_out; - 整段音频一次性过 encoder 得到
encoder_out[B, 1024, T_enc]; - 对每个编码帧,最多循环
max_symbols=10次:joiner 融合encoder_out[:,:,t:t+1]与decoder_out→ argmax → 非 blank 则更新 decoder 状态并继续发射,blank 则跳到下一帧; - 收集 token 序列,
▁替换为空格,strip()后输出文本。
五、缓冲流式导出:三种延迟档位
Parakeet Unified 的核心卖点是通过set_default_att_context_size([left, chunk, right])把非流式模型"剪"成流式。export_onnx_streaming.py 预置了三个延迟档位:
| 档位 | left | chunk | right | 对应特征帧(×8) |
|---|---|---|---|---|
1120ms | 70 | 7 | 7 | 左 560 / chunk 56 / 右 56 |
560ms | 70 | 2 | 5 | 左 560 / chunk 16 / 右 40 |
240ms | 70 | 1 | 2 | 左 560 / chunk 8 / 右 16 |
- 三个档位共享相同的左上下文(70 个编码帧,约 560 ms),保证首帧即可依赖足够的历史;
chunk越小延迟越低(chunk=1 编码帧对应 80 ms 输入帧),但依赖的右侧未来帧越少,精度与延迟的权衡点就在right;- 导出的 encoder 元数据新增:
streaming_model_type="nemo_parakeet_unified_streaming"、buffered_streaming=1、left/chunk/right_encoder_frames、left/chunk/right_feature_frames(编码帧 × 8 换算为特征帧)。decoder 也写入streaming_model_type便于识别。
该导出方式被称作buffered streaming:每次只把"左上下文 + chunk + 右上下文"的特征窗口送入 encoder,只取窗口中央 chunk 对应的编码帧参与解码,随后窗口整体向右滑动。buffered_streaming_helpers.py 的slice_feature_buffer()实现了窗口切片与首尾补零,test_onnx_streaming.py的decode_buffered()展示了逐 chunk 推理与仅取中央帧(t_start = left_encoder_frames)的解码方式。
在 sherpa-onnx 运行时侧,该模型类型由 online-recognizer-transducer-nemo-parakeet-unified-impl.h 与 online-transducer-nemo-parakeet-unified-model.cc 支持(online-recognizer-impl.cc中依据streaming_model_type == "nemo_parakeet_unified_streaming"分发),并通过元数据读取left_encoder_frames/chunk_encoder_frames/right_encoder_frames驱动流式解码;配套的贪婪搜索实现见 online-transducer-greedy-search-nemo-parakeet-unified-decoder.cc。
六、端到端验证:脚本、RTF 与产物
6.1 一键导出脚本
非流式一键流程见 run.sh:
# 1. 下载 .nemo 模型与测试音频 2086-149220-0033.wav curl -SL -O https://huggingface.co/nvidia/parakeet-unified-en-0.6b/resolve/main/parakeet-unified-en-0.6b.nemo curl -SL -O https://dldata-public.s3.us-east-2.amazonaws.com/2086-149220-0033.wav # 2. 安装依赖(NeMo、kaldi-native-fbank、onnxruntime 等) pip install "nemo_toolkit[asr] @ git+https://github.com/NVIDIA/NeMo.git" \ "numpy<2" kaldi-native-fbank librosa onnx onnxruntime soundfile # 3. 导出 + int8 量化 python3 ./export_onnx.py # 4. 冒烟测试:int8 全套 与 int8 encoder + fp32 decoder/joiner 混合 python3 ./test_onnx.py --encoder ./encoder.int8.onnx --decoder ./decoder.int8.onnx \ --joiner ./joiner.int8.onnx --tokens ./tokens.txt --wav 2086-149220-0033.wav python3 ./test_onnx.py --encoder ./encoder.int8.onnx --decoder ./decoder.onnx \ --joiner ./joiner.onnx --tokens ./tokens.txt --wav 2086-149220-0033.wav流式版本见 run-streaming.sh:对1120ms/560ms/240ms三个档位循环执行export_onnx_streaming.py --latency $latency与流式测试脚本,并将产物整理打包为sherpa-onnx-nemo-parakeet-unified-en-0.6b(-int8)-streaming-<latency>.tar.bz2(含 encoder/decoder/joiner、tokens.txt、测试音频及模型说明文档),可直接供 sherpa-onnx 使用。
6.2 实测结果(notes.md 记录)
以测试音频2086-149220-0033.wav为例,导出与推理结果完全一致:
- 识别文本:
Well, I don't wish to see it any more, observed Phoebe, turning away her eyes it is certainly very like the old portrait; - RTF(实时率,越低越快):int8 组合约
0.100,int8 encoder + fp32 decoder/joiner 混合约0.123(CPU 单线程环境),均远小于 1,满足实时推理要求; - token 输出示例:
[218, 32, 961, 34, 220, 966, 943, 7, 302, 22, ...](共 49 个非 blank token,索引范围 0–1023); - 特征形状:
features.shape (942, 128),即 942 帧 × 128 维,对应约 9.42 秒音频(含 2 秒尾静音)。
6.3 导出产物文件体积
| 文件 | 体积 |
|---|---|
| encoder.onnx + encoder.weights(fp32) | 40 MB + 2.3 GB |
| encoder.int8.onnx | 624 MB |
| decoder.onnx / decoder.int8.onnx | 28 MB / 6.9 MB |
| joiner.onnx / joiner.int8.onnx | 6.6 MB / 1.7 MB |
可见参数量集中在编码器(约 6 亿参数规模的 0.6B 命名来源),int8 量化可将 encoder 体积压缩约 4 倍(2.3 GB → 624 MB),decoder 与 joiner 同样明显瘦身,非常适合边缘设备部署。
七、总结与接入建议
Parakeet Unified EN 0.6B 在 sherpa-onnx 中的接入路径清晰完整:
- 非流式场景:直接使用 run.sh 导出的 encoder/decoder/joiner 三件套(推荐 encoder 使用 int8 以换取约 4 倍体积缩减,测试表明 RTF 反而更低);
- 流式场景:根据延迟预算选择 run-streaming.sh 中的档位——低延迟选
240ms,追求精度选1120ms; - 元数据是运行时契约:
streaming_model_type、subsampling_factor、normalize_type、left/chunk/right_encoder_frames等字段必须写入 ONNX 元数据,sherpa-onnx 的 nemo parakeet unified 实现 依赖它们完成特征归一化、状态管理与 chunk 调度; - 特征前处理不可省略:128 维 FBank(hann 窗、is_librosa)与
per_feature归一化是模型输入格式的一部分,任何一端缺失都会导致识别结果异常。
如果需要验证导出的 ONNX 与运行时行为一致,可同时运行仓库内的 test_onnx.py(非流式)与 test_onnx_streaming.py(流式),二者均以2086-149220-0033.wav为基准音频,输出与 notes.md 记录一致的文本即可确认链路无误。
【免费下载链接】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),仅供参考