YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Qwen3-ASR Runtime Library for RK3576/RK3588
v1.3.0 — Merged Encoder, Token Rollback, 代码重构 (engine.py -56%)
基于 RKNN 编码器 (NPU) + RKLLM 解码器 (NPU) 的 Qwen3-ASR 语音识别运行库。
目录结构
qwen3asr_rknn/
├── python/ # Python 运行库
│ ├── qwen3asr/ # 核心库 (v1.3.0)
│ │ ├── __init__.py # 包入口
│ │ ├── engine.py # 主引擎 (文件转录, 组件加载)
│ │ ├── stream.py # 流式识别会话 (StreamSession)
│ │ ├── encoder.py # RKNN 编码器 (merged/split 自动检测)
│ │ ├── decoder.py # RKLLM 解码器 (ctypes EMBED 模式)
│ │ ├── mel.py # Mel 频谱提取 (Whisper 兼容)
│ │ ├── vad.py # Silero VAD 封装
│ │ ├── config.py # 配置常量和默认参数
│ │ └── utils.py # 音频加载、ITN、文本处理
│ ├── transcribe.py # 文件转录 CLI
│ └── mic_stream.py # 麦克风实时识别 CLI
├── scripts/ # 辅助脚本
│ ├── test_vad_stream.py # VAD 流式识别测试
│ ├── bench_encoder.py # 编码器性能测试
│ └── onnx2rknn_*.py # 模型转换脚本
├── models/ # 模型文件 (部署时需要)
│ ├── encoder/rk3576/ # Split 编码器 (FE+BE, 旧方案)
│ ├── qwen3_asr_encoder_merged.fp16.{N}s.rk3576.rknn
│ │ # ▲ Merged 编码器 (推荐, 快 1.4-1.8x)
│ ├── rkllm/ # RKLLM 解码器
│ ├── embd/ # Embedding 表
│ ├── vad/ # Silero VAD 模型
│ ├── mel_filters.npy # Mel 滤波器
│ └── tokenizer/ # Tokenizer
├── lib/ # RKLLM 运行库 (librkllmrt.so)
├── audio/ # 测试音频
├── _transfer/ # 模型转换/测试工具
└── docs/ # 本文档
快速开始
1. 部署到新机器
# 复制整个 qwen3asr_rknn/ 目录到目标机器
scp -r qwen3asr_rknn/ user@target:/path/to/
# 确保依赖已安装
pip install numpy librosa soundfile tokenizers
# 可选: pip install pydub (支持更多音频格式)
2. 文件转录
cd /path/to/qwen3asr_rknn/python
# 基本用法
python transcribe.py --audio /path/to/audio.wav
# 指定语言和上下文
python transcribe.py --audio audio.wav --language Chinese --context "新闻节目"
# 英文识别
python transcribe.py --audio english.wav --language English
# 自动检测语言
python transcribe.py --audio audio.wav --language auto
# 更多参数
python transcribe.py --audio audio.wav \
--chunk-size 20 \
--memory-num 3 \
--cpus 4 \
--max-new-tokens 300 \
--start 10 --duration 60
3. 麦克风实时识别
# 查看可用设备
python mic_stream.py --list-devices
# 开始实时识别 (默认 USB 麦克风)
python mic_stream.py
# 自定义参数
python mic_stream.py --device plughw:2,0 --chunk-size 10 --language Chinese
# 限制时长
python mic_stream.py --max-seconds 60
4. Python API 调用
from qwen3asr import Qwen3ASREngine
# 初始化引擎
engine = Qwen3ASREngine(
model_dir="/path/to/models",
platform="rk3576",
encoder_sizes=[2, 3, 4], # 只加载需要的尺寸 (加快启动)
enabled_cpus=2, # 2 = A72 大核
repeat_penalty=1.15, # W4A16 推荐 >1.0
compact_suffix=True, # 精简提示词, 省 ~120ms/chunk
)
# --- 文件转录 ---
result = engine.transcribe(
audio="audio.wav",
language="Chinese",
chunk_size=4.0, # 4s chunk, 配合 merged encoder 最佳
memory_num=2,
rollback_tokens=2, # Token 回退防止边界错误
)
print(result["text"])
print(f"RTF: {result['stats']['rtf']:.3f}")
# --- 流式识别 ---
stream = engine.create_stream(
language="Chinese",
chunk_size=4.0,
memory_num=2,
rollback_tokens=2,
)
# 喂入音频数据 (16kHz mono float32)
import numpy as np
pcm = np.zeros(16000 * 4, dtype=np.float32) # 4秒
result = stream.feed_audio(pcm)
print(result["text"]) # 中间结果
final = stream.finish()
print(final["text"])
# --- 带 VAD 的流式识别 ---
from qwen3asr import SileroVAD
vad = SileroVAD("/path/to/models/vad/silero_vad.onnx")
stream = engine.create_stream(language="Chinese", vad=vad)
# VAD 自动过滤静音, 仅在检测到语音时触发 ASR
engine.close()
参数说明
引擎初始化参数 (Qwen3ASREngine.__init__)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
model_dir |
str | 必填 | 模型根目录 |
platform |
str | "rk3576" | 平台:rk3576 或 rk3588 |
encoder_sizes |
list | None | 加载的编码器尺寸列表,如 [2,3,4];None=全部加载 |
encoder_quant |
str | "fp16" | 编码器量化:fp16 或 int8 (int8 质量差,不推荐) |
decoder_quant |
str | "w4a16_g128" | 解码器量化类型 |
enabled_cpus |
int | 2 | RKLLM CPU 核心数 (2=A72 大核) |
max_context_len |
int | 4096 | KV 缓存最大上下文长度 |
max_new_tokens |
int | 500 | 每次生成最大 token 数 |
top_k |
int | 1 | Top-K 采样 (1=贪心, 推荐用于 ASR) |
top_p |
float | 1.0 | 核采样阈值 |
temperature |
float | 1.0 | 采样温度 |
repeat_penalty |
float | 1.15 | 重复惩罚 (W4A16 推荐 >1.0) |
frequency_penalty |
float | 0.0 | 频率惩罚 |
presence_penalty |
float | 0.0 | 存在惩罚 |
embed_flash |
int | 1 | Flash Embedding 模式 |
compact_suffix |
bool | True | 精简后缀提示词,节省 ~120ms/chunk |
decoder_callback |
Callable | None | 流式回调 callback(text, is_finish) |
转录参数
transcribe() — 文件/数组一次性转录
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
audio |
str/ndarray | 必填 | 音频文件路径或 16kHz float32 数组 |
language |
str | "Chinese" | 语言:Chinese/English/auto 等 |
context |
str | "" | 上下文描述 (提升准确率) |
chunk_size |
float | 30.0 | 音频块大小 (秒, 自动裁剪到编码器容量) |
memory_num |
int | 2 | 滑动窗口块数 (保留最近 N 块 embedding) |
rollback_tokens |
int | 5 | 前缀回退 token 数 (减少边界抖动) |
max_new_tokens |
int | 500 | 每块最大生成 token 数 |
start_second |
float | 0.0 | 起始偏移 (秒) |
duration |
float | None | 持续时间 (秒, None=全部) |
max_chunks |
int | None | 最大处理块数 |
apply_itn_flag |
bool | True | 是否应用逆文本归一化 |
create_stream() — 创建流式会话
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
language |
str | "Chinese" | 语言提示 |
context |
str | "" | 上下文描述 |
chunk_size |
float | 4.0 | 每块秒数 (流式推荐 2-5s) |
memory_num |
int | 2 | 滑动窗口大小 (≥2) |
unfixed_chunks |
int | 0 | 前 N 块不使用前缀文本 |
rollback_tokens |
int | 2 | 前缀回退 token 数 |
max_new_tokens |
int | 128 | 每块最大 token 数 |
on_text |
Callable | None | 文本回调 on_text(full_text) |
vad |
SileroVAD | None | VAD 实例,自动过滤静音 |
注意:
transcribe()和create_stream()的默认参数不同。transcribe()使用大 chunk (30s) 和较多回退 (rb=5) 适合离线;create_stream()使用小 chunk (4s) 和少量回退 (rb=2) 适合实时。
支持的语言
Chinese, English, Cantonese, Arabic, German, French, Spanish, Portuguese, Indonesian, Italian, Korean, Russian, Thai, Vietnamese, Japanese, Turkish, Hindi, Malay, Dutch, Swedish, Danish, Finnish, Polish, Czech, Filipino, Persian, Greek, Romanian, Hungarian, Macedonian
技术架构
编码器 (Encoder)
支持两种部署模式,自动检测:
Merged 模式 (推荐, v1.3+)
将 Frontend + Backend 合并为单个 RKNN 模型,减少一次 NPU 调用开销。
- 模型:
qwen3_asr_encoder_merged.fp16.{N}s.rk3576.rknn(369-371MB) - 输入:
input_features(1, 128, T_mel)+attention_mask(1, 1, T_down, T_down) - 输出:
audio_embeds(1, T_down, 1024)浮点 embedding - 性能: 比 Split 模式快 1.4-1.8x (详见性能章节)
Split 模式 (传统)
Frontend 和 Backend 分开推理:
- Frontend:
encoder_fe.fp16.{N}s.rk3576.rknn(~22MB) - Backend:
encoder_be.fp16.{N}s.rk3576.rknn(~350MB) - 流程: Mel 频谱 → Frontend (NPU) → Backend (NPU) → embedding
共同特性
- 输入: 16kHz PCM → 128-bin Mel 频谱
- Token 计算:
ceil(T_mel / 8) + 1(3层CNN子采样), 每秒 ~13 tokens - 多尺寸: 加载 2s/3s/4s/5s 等多种尺寸,运行时按音频长度自动选择最近尺寸
- INT8: 质量严重下降,不可用
解码器 (Decoder)
- 模型: Qwen3 (标准, 非 VL) 导出的 RKLLM
- 推荐量化: W4A16_G128 (GRQ), 精度与 W8A8 相当但更快
- 输入: EMBED 模式 - [前缀embedding | 音频embedding | 后缀embedding]
- 关键设置:
rkllm_set_chat_template(handle, "", "", "")- 禁用内置模板role = ""- 空角色embed_flash = 1- Flash Embeddingcompact_suffix = True- 精简后缀,省约 10 tokens / 120ms
- 采样: 默认贪心 (top_k=1) + repeat_penalty=1.15
Prompt 模板
标准模式:
<|im_start|>system
You are a helpful assistant. {context}<|im_end|>
<|im_start|>user
<|audio_start|>{AUDIO_EMBEDDING}<|audio_end|>
数字用0123456789,语音转录:<|im_end|>
<|im_start|>assistant
language {Language}<asr_text>{prefix_text}
精简模式 (compact_suffix=True):
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
<|audio_start|>{AUDIO_EMBEDDING}<|audio_end|><|im_end|>
<|im_start|>assistant
<asr_text>{prefix_text}
流式识别策略 (StreamSession)
- 音频按
chunk_size秒分块 - 每块独立编码 (NPU, 自动选择最近编码器尺寸)
- 保留最近
memory_num块的 embedding (滑动窗口) - 旧块文本作为前缀传入 LLM (
_decode_with_window()统一核心) - 前缀文本回退
rollback_tokens个 token (减少边界错误) - 可选 VAD 门控 (
_feed_with_vad()), 仅在检测到语音时触发 ASR
Token Rollback
解决分块边界处的文本错误 (如 "九百六。十七期" → "九百六十七期"):
rollback_tokens=0: 无回退rollback_tokens=2-3: 推荐, 平衡准确率与重复推理开销rollback_tokens=5:transcribe()默认, 适合离线批量
工作原理: 提交前缀文本时截掉最后 N 个 token, 让 LLM 在下一块重新生成这部分, 从而消除边界伪影。
性能参考 (RK3576, 锁频后)
锁频配置(推荐)
锁定 CPU/NPU/DDR 频率可显著提升性能稳定性 (~27% 提升):
# 锁定 CPU 大核 (需 root)
echo userspace > /sys/devices/system/cpu/cpufreq/policy4/scaling_governor
echo 2208000 > /sys/devices/system/cpu/cpufreq/policy4/scaling_setspeed
echo 2208000 > /sys/devices/system/cpu/cpufreq/policy6/scaling_setspeed
# 锁定 NPU
echo userspace > /sys/class/devfreq/fdab0000.npu/governor
echo 1000000000 > /sys/class/devfreq/fdab0000.npu/userspace/set_freq
# 锁定 DDR
echo userspace > /sys/class/devfreq/dmc/governor
echo 2736000000 > /sys/class/devfreq/dmc/userspace/set_freq
# 查看 NPU 负载
cat /sys/kernel/debug/rknpu/load
实测性能 (W4A16_G128, memory=2, 锁频)
Merged Encoder (推荐)
| Chunk | 编码 (ms) | vs Split 加速 | RTF (60s 音频) | 适用场景 |
|---|---|---|---|---|
| 2s | ~302 | 1.79x | ~0.55 | 低延迟实时 |
| 3s | ~364 | 1.55x | ~0.56 | 折中 |
| 4s | ~499 | 1.38x | ~0.57 | 流式推荐 |
| 5s | ~643 | 1.20x | ~0.58 | 质量优先 |
Split Encoder (传统)
| Chunk | FE (ms) | BE (ms) | 总编码 (ms) | RTF |
|---|---|---|---|---|
| 2s | ~60 | ~480 | ~540 | ~0.70 |
| 3s | ~60 | ~505 | ~565 | ~0.68 |
| 4s | ~60 | ~630 | ~690 | ~0.66 |
| 5s | ~60 | ~710 | ~770 | ~0.65 |
锁频 vs 未锁频
| 指标 | 锁频前 | 锁频后 | 提升 |
|---|---|---|---|
| 首 chunk 延迟 | ~2.4s | ~1.8s | ~25% |
| 后续 chunk 延迟 | ~1.8s | ~1.4s | ~22% |
| RTF (60s 音频) | 0.78 | 0.57 | 27% |
硬件限制
- 最低延迟: ~1.0s (merged encoder) / ~1.3s (split encoder)
- Python/ctypes 开销:
4% (20ms),可忽略 - INT8 encoder: 质量严重下降,不可用
- 内存: W4A16 decoder ~1GB, 单个 merged encoder ~370MB
部署检查清单
- 编码器模型: merged (
encoder_merged.fp16.*.rknn) 或 split (encoder_fe/be) - 解码器模型:
decoder_qwen3.w4a16_g128.{platform}.rkllm - 辅助文件:
mel_filters.npz,embed_tokens.npy,tokenizer.json - 运行库:
librkllmrt.so在lib/目录 - Python 依赖:
numpy,librosa,soundfile,tokenizers - RKNN runtime: ≥ 2.3.2 (
rknnlite) - 锁频: CPU 2208MHz, NPU 1000MHz, DDR 2736MHz (推荐)
- 麦克风:
arecord -l(实时场景)
故障排除
EMBED 模式只输出 1-3 个 token
- 确认使用
decoder_qwen3.*.rkllm(model_type=qwen3, 非 qwen3_vl) - 确认调用了
rkllm_set_chat_template(h, "", "", "") - 确认
role = ""(空字符串)
编码器加载失败
- 检查 RKNN runtime 版本 ≥ 2.3.2
- 检查 NPU 驱动版本 ≥ 0.9.8
- 确认模型与平台匹配 (rk3576 vs rk3588)
- Merged 模型文件名须含
merged关键字
内存不足
- W4A16 decoder 约需 1GB
- 单个 merged encoder 约 370MB
encoder_sizes=[4]只加载单个尺寸可节省内存- 减少
max_context_len可降低 KV 缓存内存
Token Rollback 不生效
- 确认
rollback_tokens ≥ 1 - 首块无前缀,回退仅对第 2 块开始生效
unfixed_chunks > 0会延迟前缀使用
流式结果有重复文字
- 增大
repeat_penalty(推荐 1.15-1.3) - 检查
memory_num ≥ 2 - 适当增大
rollback_tokens(2-3)
- Downloads last month
- 110
4-bit