GigaAM 俄语语音识别模型转换指南:从 NeMo/GigaAM 到 sherpa-onnx 的 CTC 与 RNNT 全流程
【免费下载链接】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/GigaAM/README.md 所述的核心任务展开:将 SberDevices 开源的GigaAM 俄语语音识别模型(包含 CTC 与 RNNT 两类架构)转换为 sherpa-onnx 可加载的 ONNX 格式。读完本文,你将掌握从环境搭建、权重下载、ONNX 导出、INT8 量化到最终在 sherpa-onnx 中离线推理的完整实战链路,并理解转换脚本与推理端 C++ 实现之间的元数据约定。
一、背景:GigaAM 与 sherpa-onnx 的衔接
GigaAM 是一系列面向俄语语音识别的模型集合(由 salute-developers 团队开源)。本仓库的 scripts/nemo/GigaAM 目录提供了一整套转换脚本,负责将 GigaAM 模型从 PyTorch/NeMo 生态迁移到基于 onnxruntime 的 sherpa-onnx 推理框架。
这些模型之所以能无缝衔接 sherpa-onnx,核心在于转换脚本会向导出的 ONNX 模型写入一组元数据(meta data),例如model_type、vocab_size、subsampling_factor、is_giga_am等。推理端在加载模型时会读取这些元数据来决定特征提取方式、词表大小与解码策略(详见后文 八、与 sherpa-onnx 推理端的集成)。
该目录覆盖两类主流模型架构:
| 架构 | 导出脚本 | 说明 |
|---|---|---|
| CTC | export-onnx-ctc*.py | 字符级或 BPE 词表,单模型文件,输出logprobs |
| RNNT | export-onnx-rnnt*.py | 拆分为 encoder / decoder / joiner 三个 ONNX 文件 |
二、目录结构与脚本总览
scripts/nemo/GigaAM 下的脚本按"导出(export)— 运行(run)— 测试(test)"三段式组织,并存在v1 / v2 / v3 / v3-punct等多个模型版本变体:
| 文件 | 用途 |
|---|---|
export-onnx-ctc.py | 从 NeMoEncDecCTCModel导出 CTC 模型(v1,基于 NeMo 工具链) |
export-onnx-ctc-v2.py | 基于官方gigaamPython 包导出 v2 CTC 模型 |
export-onnx-ctc-v3.py | 导出 v3 CTC 模型(字符级,34 类输出) |
export-onnx-ctc-v3-punct.py | 导出 v3 带标点 CTC 模型(BPE,257 类输出) |
export-onnx-rnnt.py | 从 NeMoEncDecRNNTBPEModel导出 RNNT 三分模型(v1) |
export-onnx-rnnt-v2.py/export-onnx-rnnt-v3.py/export-onnx-rnnt-v3-punct.py | 对应 v2 / v3 / v3-punct 的 RNNT 版本 |
run-ctc.sh/run-rnnt.sh及 v2/v3 变体 | 一键执行:装环境 → 下权重 → 导出 → 测试 |
test-onnx-ctc.py/test-onnx-rnnt.py | 用 onnxruntime 验证导出结果并打印识别文本 |
其中test-onnx-ctc.py与test-onnx-rnnt.py对所有 CTC / RNNT 版本通用,脚本顶部注释与模型 IO 打印信息(display函数)直接反映了各版本导出的 ONNX 图结构。
三、环境准备与一键转换流程
以 v1 版本为例,run-ctc.sh 与 run-rnnt.sh 的流程完全一致,分为三步:安装依赖 → 下载模型文件 → 执行导出与测试。
3.1 依赖安装(install_nemo)
CTC 与 RNNT 的 v1 脚本共用同一套环境(见run-ctc.sh第 6-21 行):
pip install torch==2.4.0 torchaudio==2.4.0 -f https://download.pytorch.org/whl/torch_stable.html pip install -qq wget text-unidecode "matplotlib>=3.3.2" onnx onnxruntime==1.17.1 \ pybind11 Cython einops kaldi-native-fbank soundfile librosa pip install -qq ipython python3 -m pip install git+https://github.com/NVIDIA/NeMo.git@main#egg=nemo_toolkit[asr] pip install numpy==1.26.4关键点说明:
- onnxruntime 固定为 1.17.1,与后续动态量化 API(
quantize_dynamic)及导出兼容性相关; kaldi-native-fbank用于测试阶段的 Fbank 特征提取(与 sherpa-onnx 推理端使用的特征提取库一致,保证数值对齐);numpy==1.26.4在 NeMo 安装之后单独固定,避免依赖冲突;- 脚本中注释掉了
apt-get install sox libsndfile1 ffmpeg,如系统缺失相关音频库可自行启用。
而v2 / v3 脚本(如 run-ctc-v2.sh)不再依赖 NeMo 工具链,改为直接安装 GigaAM 官方 Python 包:
python3 -m pip install git+https://github.com/salute-developers/GigaAM.git@main#egg=gigaam python3 -m pip install -qq kaldi-native-fbank pip install numpy==1.26.4v2/v3 脚本还直接下载了example.wav与模型仓库的LICENSE文件,说明新版模型由官方 gigaam 包内置下载与加载逻辑。
3.2 模型文件下载(download_files)
v1 的 CTC 与 RNNT 所需文件不同:
- CTC:
ctc_model_weights.ckpt、ctc_model_config.yaml、example.wav、long_example.wav、GigaAM License_NC.pdf; - RNNT:除
rnnt_model_weights.ckpt、rnnt_model_config.yaml及音频样本外,还额外需要tokenizer_all_sets.tar(BPE 分词器),下载后解压得到tokenizer_all_sets目录:
curl -SL -O <rnnt_model_weights.ckpt 下载地址> curl -SL -O <rnnt_model_config.yaml 下载地址> curl -SL -O <example.wav> && curl -SL -O <long_example.wav> curl -SL -O <tokenizer_all_sets.tar> tar -xf tokenizer_all_sets.tar && rm tokenizer_all_sets.tar注意:
GigaAM License_NC.pdf文件名中的NC代表 Non-Commercial(非商业)许可,使用前务必核对模型许可条款(详见 九、许可与注意事项)。
3.3 一键执行
# CTC v1 bash run-ctc.sh # RNNT v1 bash run-rnnt.sh脚本末尾依次执行导出、ls -lh查看产物、运行测试脚本。其中run-rnnt.sh在测试后还会删除中间产物encoder.onnx(仅保留量化后的encoder.int8.onnx)。
四、CTC 模型导出:字符级词表与 INT8 量化
4.1 v1:基于 NeMo 工具链导出
export-onnx-ctc.py 的核心流程:
model = EncDecCTCModel.from_config_file("./ctc_model_config.yaml") ckpt = torch.load("./ctc_model_weights.ckpt", map_location="cpu") model.load_state_dict(ckpt, strict=False) model.eval() # 生成 tokens.txt:字符级词表,space 的 id 为 0,<blk> 为最后一个 token with open("tokens.txt", "w", encoding="utf-8") as f: for i, t in enumerate(model.cfg.labels): f.write(f"{t} {i}\n") f.write(f"<blk> {i+1}\n") model.export("model.onnx")转换要点:
strict=False加载权重,容忍配置与 checkpoint 之间的键差异;- 词表规则:GigaAM CTC 模型为字符级词表,空格的 token id 固定为0,
<blk>(blank)追加在最后一个 id;这一约定在测试脚本中通过blank = len(id2token) - 1呼应; - 导出后立即为
model.onnx写入元数据:
meta_data = { "vocab_size": len(model.cfg.labels) + 1, "normalize_type": "", # 特征不做归一化 "subsampling_factor": 4, # 时间维下采样 4 倍 "model_type": "EncDecCTCModel", "version": "1", "model_author": "https://github.com/salute-developers/GigaAM", "license": "https://github.com/salute-developers/GigaAM/blob/main/GigaAM%20License_NC.pdf", "language": "Russian", "is_giga_am": 1, # 标记为 GigaAM 模型,推理端据此调整特征参数 }其中is_giga_am: 1是 sherpa-onnx 推理端识别 GigaAM 模型的关键标志(详见第八节)。随后调用 onnxruntime 的动态量化生成 INT8 版本:
quantize_dynamic( model_input="model.onnx", model_output="model.int8.onnx", weight_type=QuantType.QUInt8, )最终目录下同时保留model.onnx(FP32)与model.int8.onnx(INT8),供不同精度需求的部署场景选用。
4.2 前处理适配:Mel 谱与 NeMo 的参数兼容
导出脚本中定义了两个子类FilterbankFeaturesTA与AudioToMelSpectrogramPreprocessor(export-onnx-ctc.py第 18-54 行),用于在导出阶段注入与 sherpa-onnx 一致的 Mel 特征计算参数:
- 丢弃 NeMo 传入的
window_size/window_stride,改用显式构造的torchaudio.transforms.MelSpectrogram; - 特征参数:
n_fft=320、win_length=320、hop_length=160、mel_scale="htk"、nfilt=64; - 将 NeMo 的
features参数重映射为nfilt,保证配置兼容。
这些参数与测试脚本、C++ 推理端的特征提取配置一一对应(见第六节),是"导出即对齐"的关键。
4.3 v2 / v3 / v3-punct 变体
- v2(export-onnx-ctc-v2.py):改用
gigaam.load_model("v2_ctc"),通过model.to_onnx(".")导出,词表同样来自model.cfg["labels"],产出文件名为v2_ctc.onnx,随后量化为统一的model.int8.onnx; - v3(export-onnx-ctc-v3.py):词表取自
model.cfg["decoding"]["vocabulary"],配置注释中给出了完整的 v3 模型结构:ConformerEncoder,16 层,d_model 768,8 倍 FFN 扩展,16 头 rotary 自注意力,conv1d 下采样核 5,CTC 输出 34 类(空格 + 32 个俄语字符 + blank); - v3-punct(export-onnx-ctc-v3-punct.py):带标点版本
v3_e2e_ctc,输出维度提升到257(BPE 词表),词表通过model.decoding.tokenizer.model(sentencepiece 模型)生成,并在元数据中额外写入"comment": "v3"以区分版本。
五、RNNT 模型导出:encoder / decoder / joiner 三分结构
RNNT(Transducer)架构与 CTC 不同,export-onnx-rnnt.py 将模型拆分为三个独立 ONNX 文件:
model = EncDecRNNTBPEModel.from_config_file("./rnnt_model_config.yaml") # ... model.encoder.export("encoder.onnx") model.decoder.export("decoder.onnx") model.joint.export("joiner.onnx")要点:
- 词表:RNNT 使用 BPE 词表,直接取自
model.joint.vocabulary,同样把<blk>追加为最后一个 id,写入tokens.txt; - 元数据写入 encoder.onnx,并额外包含解码器结构信息:
meta_data = { "vocab_size": model.decoder.vocab_size, # 不含 blank,C++ 端会自行加 1 "pred_rnn_layers": model.decoder.pred_rnn_layers, # 预测网络 LSTM 层数 "pred_hidden": model.decoder.pred_hidden, # 预测网络隐藏维度 "normalize_type": "", "subsampling_factor": 4, "model_type": "EncDecRNNTBPEModel", "language": "Russian", "is_giga_am": 1, ... }- 量化仅针对encoder(
encoder.int8.onnx),decoder 与 joiner 保持 FP32; test-onnx-rnnt.py通过meta["pred_rnn_layers"]/meta["pred_hidden"]动态构造 decoder 的初始 LSTM 状态(见第七节)。
六、特征提取协议:Fbank 参数与模型输入输出
6.1 Fbank 特征参数(导出端、测试端、推理端三方一致)
两个测试脚本中的create_fbank()完全一致,定义了 GigaAM 系列模型的统一特征协议:
opts.frame_opts.dither = 0 opts.frame_opts.remove_dc_offset = False opts.frame_opts.preemph_coeff = 0 opts.frame_opts.window_type = "hann" opts.frame_opts.round_to_power_of_two = False opts.mel_opts.low_freq = 0 opts.mel_opts.high_freq = 8000 opts.mel_opts.num_bins = 64 # 64 维 Fbank音频统一按16 kHz单声道处理(fbank.accept_waveform(16000, audio)),特征维度为64。这一数值与 C++ 推理端源码注释相互印证:offline-transducer-nemo-model.cc 中明确写道 "giga am uses 64",即 GigaAM 使用 64 维特征,而 parakeet-tdt-0.6b-v2 用 128 维、其余 NeMo 模型默认 80 维——推理端正是依据is_giga_am元数据动态选择特征维度。
6.2 CTC 模型的 IO 协议
test-onnx-ctc.py中display()打印的 ONNX 图结构(脚本第 59-65 行注释):
Input : audio_signal (float) [batch, 64, T] # 特征矩阵 [C, T] length (int64) [batch] # 帧数 Output: logprobs (float) [batch, T', 34] # T' = T / subsampling_factor推理时需将[T, C]的特征转置为[1, C, T],并传入 int64 的帧长度:
x = torch.from_numpy(x).t().unsqueeze(0) # [1, C, T] x_lens = torch.tensor([x.shape[-1]], dtype=torch.int64) log_probs = self.model.run([output_name], {input_name: x.numpy(), length_name: x_lens.numpy()})[0]6.3 RNNT 模型的 IO 协议
test-onnx-rnnt.py打印的三段图结构(脚本第 53-75 行注释):
- encoder:输入
audio_signal [1, 64, T]与length (int64);输出outputs [1, 768, T'](d_model=768)与encoded_lengths; - decoder(预测网络):输入
targets (int32) [1, 1]、target_length (int32)、两组 LSTM 状态[1, 1, 320];输出outputs [1, 320, 1]与更新后的状态——即320 维、可迭代的 RNN 状态机; - joiner:输入 encoder 输出
[1, 768, 1]与 decoder 输出[1, 320, 1],输出[1, 1, 1, 513](vocab_size 含 blank)。
七、测试脚本:验证导出的 ONNX 模型
7.1 CTC 解码测试
test-onnx-ctc.py 加载model.int8.onnx与tokens.txt,用soundfile读取example.wav(多声道时取第一通道,非 16 kHz 时用librosa.resample重采样),随后:
- 用 kaldi-native-fbank 计算 64 维 Fbank;
- 前向得到
log_probs [1, T', 34]; - 执行贪心 CTC 解码:
argmax取每帧最佳 token,跳过 blank 并合并相邻重复 token:
blank = len(id2token) - 1 prev = -1 for i in ids: if i != blank and i != prev: ans.append(i) prev = i最终把 token id 序列映射回俄语字符并拼接打印。
7.2 RNNT 帧同步解码测试
test-onnx-rnnt.py 实现的是帧同步(frame-sync)贪心 Transducer 解码,流程更贴近 sherpa-onnx 的流式/离线推理逻辑:
- 在音频尾部拼接2 秒静音(
tail_padding = np.zeros(sample_rate * 2)),保证尾部 token 有足够帧触发 joiner 输出; - 用全零初始化 decoder 的 LSTM 状态(
pred_rnn_layers × batch × pred_hidden); - 先用 blank token 走一次 decoder 获得初始
decoder_out; - 对 encoder 输出的每一帧
t:将encoder_out[:, :, t:t+1]与当前decoder_out送入 joiner,argmax后若结果不是 blank,则把新 token 追加进答案并用它更新 decoder 状态,继续下一轮; - 最后移除开头的 blank,将 BPE token(
▁下划线前缀代表词边界)还原为空格分隔的俄语文本:
text = "".join(tokens).replace("▁", " ").strip()这套"encoder 一次性前向 + decoder 逐帧迭代"的流程,正是 sherpa-onnx 中 offline-transducer-nemo-model.cc 加载 RNNT 模型后的推理范式缩影。
八、与 sherpa-onnx 推理端的集成
导出脚本写入的元数据在推理端被显式读取:
- offline-nemo-enc-dec-ctc-model.cc 读取
vocab_size、subsampling_factor、normalize_type,并通过SHERPA_ONNX_READ_META_DATA_WITH_DEFAULT(is_giga_am_, "is_giga_am", 0)读取 GigaAM 标志; - offline-transducer-nemo-model.cc 对 RNNT 模型做同样处理,并依据
is_giga_am决定feat_dim_取 64(见该文件第 321-324 行注释)。
is_giga_am的默认值为 0,因此只有经过本目录脚本导出的模型才会携带该标志并触发 GigaAM 专用特征参数,其余 NeMo 模型不受影响——这正是"转换脚本决定推理行为"的设计所在。
在 Python API 侧,导出的模型可直接通过OfflineRecognizer.from_nemo_ctc加载使用,参见 offline-nemo-ctc-decode-files.py:
recognizer = sherpa_onnx.OfflineRecognizer.from_nemo_ctc( model=model, # model.onnx 或 model.int8.onnx tokens=tokens, # tokens.txt debug=True, # 打印模型元数据便于核对 ) stream = recognizer.create_stream() stream.accept_waveform(sample_rate, audio) # 任意采样率均可,内部自动处理 recognizer.decode_stream(stream)from_nemo_ctc会根据 ONNX 内的元数据自动选择 CTC 解码逻辑;RNNT 模型则对应OfflineRecognizer.from_transducer系列的 NeMo 加载路径。
九、许可与注意事项
- 模型许可:GigaAM 模型附带
GigaAM License_NC.pdf(Non-Commercial 许可),转换与部署前务必逐条核对许可条款(原始 README 亦指向模型仓库的 LICENSE 文件,见 scripts/nemo/GigaAM/README.md 末尾说明);导出脚本会把许可信息写入 ONNX 元数据的license字段,便于模型分发时追溯。 - 版本对应关系:v1 脚本依赖 NVIDIA NeMo 工具链 + 独立 checkpoint/config 文件;v2/v3 依赖官方
gigaamPython 包,模型权重由包内下载机制管理。选择脚本时请与目标模型版本严格对应,混用会导致配置与权重不匹配(加载时使用strict=False只能容忍少量键差异)。 - 量化产物:INT8 量化仅覆盖权重(动态量化),encoder 是 RNNT 中计算量最大的部分,因此优先量化 encoder;若追求更高精度可继续使用 FP32 的
model.onnx/encoder.onnx。 - 特征对齐:导出、测试、推理三端的 Fbank 参数(16 kHz、64 维、hann 窗、0–8 kHz 频带)必须保持一致,任何一端改动都会破坏识别结果;这也是
is_giga_am元数据在 C++ 端被强制读取的原因。
【免费下载链接】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),仅供参考