sherpa-onnx流式语音识别RK3566部署实战指南与RKNN避坑
【免费下载链接】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
sherpa-onnx 是一个基于 ONNX Runtime 的离线语音工具链,核心能力包括流式语音识别、文本转语音、VAD 端点检测和说话人分离,支持 12 种编程语言,无需联网即可运行。我们在一块 RK3566 四核开发板(4 核 Cortex-A55 @ 2.0GHz,4GB 内存)上,用它的 RKNN 后端(RKNN 是 Rockchip NPU 的运行时,负责把量化后的模型调度到 NPU 上执行)跑通了 zipformer 流式识别模型,实测 RTF 0.35。这篇文章记录从段错误到跑通的完整排错过程,以及踩过的每一个坑。
段错误先撞上 rknn_run:RK3566 上的第一堵墙
第一次在板子上直接跑流式识别,程序没有任何识别输出就退出了:
./build/bin/sherpa-onnx --provider=rknn ... Segmentation fault (core dumped)一开始我也以为是板子内存不够——RK3566 只有 4GB LPDDR4,加载模型时 free 里可见内存掉得很凶。但把换行输出重定向后反复试,故障稳定复现,说明不是偶发的内存抖动。用 GDB 挂了调试器,才拿到真正的堆栈:
gdb ./build/bin/sherpa-onnx (gdb) run --provider=rknn --transducer-encoder=encoder.rknn ... test.wav Program received signal SIGSEGV, Segmentation fault. #0 rknn_run (...) from /usr/lib/librknnrt.so (gdb) bt关键信息是#0落在librknnrt.so的rknn_run内部,而不是 sherpa-onnx 自己的代码里。这个结论很重要:崩溃发生在 RKNN 运行时库内部,大概率是运行时版本与模型格式不匹配,sherpa-onnx 侧的逻辑基本可以排除。怎么验证的?把同样的命令换成--provider=cpu用 ONNX 原版模型跑,同一份音频顺利出识别结果——模型本身和参数都没问题,问题锁定在 RKNN 这条路径上。
排错日志:三个 RKNN 版本,只有 2.2.0 能用
为了确认是版本问题,我们把 rknn-toolkit2 的 2.1.0、2.2.0、2.3.2 三个版本在板子上各装了一遍,跑完全相同的模型和命令:
| RKNN 运行时版本 | 运行结果 | 具体表现 |
|---|---|---|
| 2.1.0 | 报错退出 | 推理时报 "Meet unsupported input dtype for gather",gather 算子的输入数据类型不被支持 |
| 2.2.0 | 正常 | 全程无异常,识别结果与 CPU 后端一致 |
| 2.3.2 | 段错误 | 段错误复现,GDB 堆栈同样指向rknn_run内部 |
结论是必须固定在 2.2.0。2.1.0 的 gather 算子 dtype 校验过严,2.3.2 则退化成段错误,都是运行时库与模型序列化格式之间的底层兼容问题,应用层无法绕过。验证方法就是上表的做法:模型不动、命令不动,只换运行时版本,三组结果一比即知。装的时候注意 2.2.0 需要装配套的 Python 依赖(pip3 install -r requirements.txt后再pip3 install .),少一步都会让工具链行为异常。
编译 sherpa-onnx:一个 CMake 开关和一个环境变量
sherpa-onnx 的 RKNN 适配层是独立编译的:流式/离线模型的 RKNN 推理实现集中在 sherpa-onnx/csrc/rknn/,包含 zipformer 流式 transducer、paraformer 离线模型、silero VAD 等,默认不编进主库。编译开关定义在 sherpa-onnx/csrc/CMakeLists.txt,打开后会把上述源文件加入sherpa-onnx-core并链接rknnrt。
在源码根目录执行克隆与配置(板端直接编译,省去交叉工具链):
git clone https://gitcode.com/GitHub_Trending/sh/sherpa-onnx cd sherpa-onnx && mkdir build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DSHERPA_ONNX_ENABLE_C_API=ON \ -DSHERPA_ONNX_ENABLE_RKNN=ON \ -DSHERPA_ONNX_RKNN_TOOLKIT2_LIB_DIR=/opt/rknn-toolkit2/lib make -j4关键配置项:
-DSHERPA_ONNX_ENABLE_RKNN=ON:打开 RKNN 后端,这是段错误排查中最容易被漏掉的一项——没开这个开关编译出的二进制,--provider=rknn根本不可用SHERPA_ONNX_RKNN_TOOLKIT2_LIB_DIR:指向板端 rknn-toolkit2 的 lib 目录,CMake 用它定位librknnrt.so,指向错会直接链接失败- 可用后端清单在 sherpa-onnx/csrc/provider-config.cc 中注册,
cpu, cuda, coreml, trt, rknn, qnn,改 provider 行为先看这个文件 - 模型扩展名路由在 sherpa-onnx/csrc/online-model-config.cc:配置项里出现
.rknn后缀就会走 RKNN 实现,所以 encoder/decoder/joiner 三个文件都要是.rknn
把 zipformer 流式模型转成 .rknn 并跑通
为什么只选流式模型。sherpa-onnx 里流式(online)模型采用分块(chunk-based)架构,encoder 按固定大小的音频块增量计算,内存占用小、首包延迟低;离线(offline)模型需要一次性喂入整段音频的中间张量,在 RK3566 上内存和 NPU 都吃紧,我们实测离线模型在板端无法稳定运行。所以部署到 RK3566,直接锁定 zipformer 流式双语(中英)模型。
模型转换。从 sherpa-onnx 官方模型仓库下载 zipformer-bilingual-zh-en 的 encoder.onnx、decoder.onnx、joiner.onnx 和 tokens.txt,用 rknn-toolkit2 2.2.0 逐一转换(在转换机执行,每个文件跑一遍):
from rknn.api import RKNN rknn = RKNN() rknn.load_onnx(model='encoder.onnx') rknn.config(target_platform='rk3566') rknn.build(do_quantization=True, dataset='dataset.txt') rknn.export_rknn('encoder.rknn')do_quantization=True开启 INT8 量化,这是上 NPU 加速的前提,量化数据集用几百段普通语音的 wav 列表即可target_platform必须写成rk3566,写rk3588等其它平台导出的模型在 3566 上会直接加载失败
板端运行命令。转换得到的三个.rknn文件与 tokens.txt 拷到板子,执行:
./build/bin/sherpa-onnx \ --provider=rknn \ --transducer-encoder=encoder.rknn \ --transducer-decoder=decoder.rknn \ --transducer-joiner=joiner.rknn \ --tokens=tokens.txt \ --num-threads=4 \ test.wav--provider=rknn:让推理走 NPU 后端而不是 CPU--num-threads=4:与 RK3566 的 4 个 A55 核数对齐,前处理/后处理线程超过核数只会增加上下文切换开销--chunk-size不用显式传,zipformer 流式模型按内置 chunk 配置增量解码
效果验证:RTF 0.35 在 RK3566 上意味着什么
用一段 10 秒的中文 wav 实测,数据如下(同一块板、RKNN 2.2.0、INT8 量化模型):
| 指标 | 数值 | 说明 |
|---|---|---|
| 模型加载时间 | 约 1.2 秒 | 三个 .rknn 文件加载进 NPU 内存 |
| 持续识别延迟 | 约 0.15 秒 | 流式模式下单块音频的处理耗时 |
| 实时因子 RTF | 0.35 | 处理 1 秒音频耗时 0.35 秒,小于 1 即满足实时 |
| 峰值内存 | 约 180MB | /proc/self/status 里 VmHWM 峰值 |
| CPU 利用率 | 约 75% | 4 核均值,NPU 分担主推理 |
RTF 0.35 的实际含义:RK3566 处理语音的速度是实时语速的约 3 倍,边说边识别有充足余量,即使 NPU 与 CPU 抢占也大概率不掉帧。验证方法很简单:time包住整段推理,用 Elapsed 除以音频时长即为 RTF。
作为对照,sherpa-onnx 的 Web 演示端(Python 包自带)在桌面端跑同一框架的界面如下,上传文件或实时录音都能出识别结果,板端跑通的正是同一套 C++ 内核:
同一套内核的跨平台表现也顺带验证了:官方 Flutter 示例的 TTS 应用直接在 Ubuntu 和 macOS 上跑,macOS 端的合成日志里能看到 RTF 0.305 这类逐次输出,说明框架在多平台上行为一致,RK3566 上跑通后迁移到别的平台成本很低。
如果你也卡在 RK3566 上:优先查这 3 点
折腾了两天最大的体会是:RK3566 上的部署问题 90% 出在运行时链路,而不是模型本身。如果你复现时卡住,按顺序查:
- RKNN 运行时是不是 2.2.0。2.1.0 报 "Meet unsupported input dtype for gather",2.3.2 直接段错误且堆栈指向
rknn_run——看到这两种现象先查版本,不要在模型上花时间。 - 模型是不是流式 + 三个 .rknn 齐全。离线模型在 3566 上跑不稳;transducer 三件套(encoder/decoder/joiner)任何一个还是 .onnx,配置路由就会退回或不匹配,识别结果异常且难排查。
- 编译开关和环境变量是否生效。改过
SHERPA_ONNX_RKNN_TOOLKIT2_LIB_DIR后必须删掉 CMakeCache.txt 重跑 cmake,否则新值不会进入链接行;用ldd build/bin/sherpa-onnx | grep rknnrt确认动态库解析到的是 2.2.0 的 librknnrt.so,而不是系统里残留的旧版本。
最后留一个隔离手段:怀疑模型或参数问题时,先用--provider=cpu加原版 .onnx 模型跑一遍,跑通再切 RKNN——这一步能立刻把"模型问题"和"运行时问题"切开,比翻日志快得多。
【免费下载链接】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),仅供参考