YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Whisper 语音识别 + Helsinki-NLP 中英翻译(RK3588)
基于 OpenAI Whisper 实现中文语音识别,配合 Helsinki-NLP(MarianMT)实现中译英翻译,模型均转换为 RKNN 格式,部署在 RK3588 NPU 上运行。
目录结构
.
├── export_onnx.py # 从 Whisper 官方模型导出 encoder/decoder 的 ONNX 文件
├── whisper_lite.py # 板端推理脚本:RKNNLite 加载 encoder/decoder,做语音识别
├── mic_whisper.py # PC 端 demo:录音 + Whisper 识别(openai-whisper 库,非板端)
├── whisper_encoder_base.onnx # Whisper Base 音频编码器(ONNX 原始版)
├── whisper_encoder_base.rknn # Whisper Base 音频编码器(RKNN 转换版,板端使用)
├── whisper_decoder_base.onnx # Whisper Base 文本解码器(ONNX 原始版)
├── whisper_decoder_base.rknn # Whisper Base 文本解码器(RKNN 转换版,板端使用)
└── Helsinki-NLP-onnx/ # Helsinki-NLP-onnx.zip 解压后
├── config.json # MarianMT 模型配置(d_model=512, 6层encoder/6层decoder)
├── generation_config.json # 生成参数(num_beams=6, max_length=512)
├── tokenizer_config.json # MarianTokenizer 配置,source_lang=zho, target_lang=eng
├── special_tokens_map.json # 特殊token映射(eos/pad/unk)
├── vocab.json # 词表
├── source.spm # 源语言(中文)sentencepiece 分词模型
├── target.spm # 目标语言(英文)sentencepiece 分词模型
├── model_fixed.onnx # 固定长度导出的翻译模型(ONNX 原始版)
└── model_fixed.rknn # 固定长度翻译模型(RKNN 转换版,板端使用)
一、Whisper 语音识别
1. 导出模型(PC 端)
python export_onnx.py --model_type base
生成 whisper_encoder_base.onnx 和 whisper_decoder_base.onnx。
2. 转换为 RKNN
使用 RKNN-Toolkit2 将上一步的 onnx 转换、量化为 .rknn(转换脚本不在本项目范围内,需另行编写)。
3. 板端推理
需要 rknnlite 运行环境,另外需准备:
mel_80_filters.txt(Mel 滤波器系数)vocab_zh.txt(中文词表)
python3 whisper_lite.py \
--encoder_model_path whisper_encoder_base.rknn \
--decoder_model_path whisper_decoder_base.rknn \
--task zh \
--audio_path test_zh.wav
推理逻辑:音频 → log-mel 频谱(pad/trim 到固定帧数)→ encoder 编码 → decoder 自回归解码(起始 token 为 SOT + LANG + TRANSCRIBE + NO_TIMESTAMPS,逐 token 生成直到 EOT 或达到 MAX_NEW_TOKENS)→ 查词表得到文本。
4. PC 端快速测试(不涉及 RKNN)
python mic_whisper.py
用官方 openai-whisper 库直接录音识别,用于验证效果,不用于板端部署。
二、Helsinki-NLP 中译英翻译
模型说明
- 语言方向固定为 中 → 英(
source_lang=zho,target_lang=eng)。 model_fixed中的"fixed"表示该模型使用固定输入长度导出——RKNN 转换要求静态 shape,推理前需将输入 pad/truncate 到该固定长度。- 分词使用 sentencepiece(
source.spm/target.spm),不是简单的空格切词。
推理流程
- 中文文本 → 用
source.spm编码为 token id 序列,pad/truncate 到固定长度。 - token 序列送入
model_fixed.rknn(RKNNLite)推理。 - 输出 token id 序列 → 用
target.spm解码为英文文本。
注意 config.json 中 decoder_start_token_id=65000、pad_token_id=65000 相同,需正确处理起始 token 与 padding 位置,避免与真实内容混淆。
依赖环境
| 用途 | 依赖 |
|---|---|
| 板端推理(Whisper + 翻译) | rknnlite |
| PC 端 Whisper demo | openai-whisper、sounddevice、numpy、scipy |
| 翻译分词/解码 | sentencepiece |
| 模型导出 | onnx、onnxsim、torch |
注意事项
whisper_lite.py中解码器自回归解码上限为MAX_NEW_TOKENS = 128(另有版本用224固定长度,视导出配置而定),可按需调整。task参数目前支持zh/en,对应不同语言 token(LANG_ZH/LANG_EN)和词表文件(vocab_zh.txt等)。- 如需支持英译中或其他语言对,需重新从 Hugging Face 导出对应的 Helsinki-NLP 模型(如
Helsinki-NLP/opus-mt-en-zh)并重新转换 RKNN。 - 分享/上传前建议检查
export_onnx.py及config.json中_name_or_path字段写死的本地路径(如/root/...),按需替换为通用路径,避免暴露服务器目录结构。
Helsinki-NLP 转 RKNN 的完整流程(补充)
1. PyTorch → ONNX(PC/WSL 端)
用 export_helsinki_onnx_fixed.py:
python export_helsinki_onnx_fixed.py
- 加载本地 MarianMT 模型(
MarianMTModel+MarianTokenizer) - 固定序列长度
FIXED_LEN = 64 - 构造三个
(1, 64)的 dummy 输入:input_ids、attention_mask、decoder_input_ids(decoder_input_ids 全部填充为decoder_start_token_id=65000) - 对整个 model 一次性导出(不分离 encoder/decoder),输出名为
logits,opset_version=14 - 生成
model_fixed.onnx
2. ONNX → RKNN
直接复用 RKNN Model Zoo 官方 whisper 例程自带的通用转换脚本 convert.py(对任意 onnx 模型通用,不是专门为 NLP 写的):
python3 rknn_model_zoo-main/examples/whisper/python/convert.py model_fixed.onnx rk3588
convert.py 核心逻辑(无需量化,走 fp):
from rknn.api import RKNN
rknn = RKNN(verbose=False)
rknn.config(target_platform='rk3588')
rknn.load_onnx(model='model_fixed.onnx')
rknn.build(do_quantization=False) # 默认不量化
rknn.export_rknn('model_fixed.rknn')
rknn.release()
转换过程中会出现 channel is too large, may produce thousands of regtask, fallback to cpu 和 Unkown op target: 0 之类的 E 级提示,属于部分算子自动 fallback 到 CPU 计算,不是致命错误,最终仍会生成完整可用的 model_fixed.rknn(约 216MB)。
3. 板端推理(对应 translate_lite.py)
板子上用 RKNNLite + MarianTokenizer(而不是 sentencepiece 原始接口)做实际推理:
from rknnlite.api import RKNNLite
from transformers import MarianTokenizer
FIXED_LEN = 64
tokenizer = MarianTokenizer.from_pretrained(MODEL_DIR, local_files_only=True)
rknn = RKNNLite()
rknn.load_rknn(f"{MODEL_DIR}/model_fixed.rknn")
rknn.init_runtime()
def translate(text):
inputs = tokenizer([text], return_tensors="np", padding="max_length",
truncation=True, max_length=FIXED_LEN)
input_ids = inputs["input_ids"].astype(np.int64)
attention_mask = inputs["attention_mask"].astype(np.int64)
decoder_input_ids = np.full((1, FIXED_LEN), 65000, dtype=np.int64) # decoder_start_token
outputs = rknn.inference(inputs=[input_ids, attention_mask, decoder_input_ids])
logits = outputs[0]
token_ids = []
for i in range(logits.shape[1]):
tid = int(np.argmax(logits[0, i, :]))
if tid == 0: # eos/pad
break
token_ids.append(tid)
return tokenizer.decode(token_ids, skip_special_tokens=True)
注意:当前这版 translate_lite.py 是逐句一次性推理,不是真正的自回归解码(decoder_input_ids 全程固定为起始 token,没有把上一步生成的 token 喂回去再生成下一个),属于简化版验证脚本,翻译质量可能不如标准自回归生成,后续如果需要提升翻译效果,可以考虑改成逐 token 循环喂入的自回归方式。