SenseVoice模型静态导出与QNN编译完整指南
概述
本文档详细介绍如何将SenseVoice语音识别模型从PyTorch格式导出为静态ONNX模型,并进一步编译为适配高通硬件平台的QNN-ONNX模型的完整流程。该流程适用于QCS8550等高通芯片平台,能够充分利用硬件加速能力提升推理性能。
1. 环境准备
1.1 依赖安装
# 安装基础依赖
pip install torch onnx funasr-onnx modelscope
# 安装Qualcomm AI Hub
pip install qai_hub
# 安装音频处理依赖
pip install librosa numpy
1.2 模型准备
确保已经下载了SenseVoice模型文件:
# 模型下载
modelscope download --model iic/SenseVoiceSmall --local_dir ./
# 模型目录结构
./models/ASR/SenseVoice-Small/
├── model.pt
├── config.yaml
├── am.mvn
└── chn_jpn_yue_eng_ko_spectok.bpe.model
2. 静态模型导出
2.1 静态导出配置
静态导出需要固定输入shape,这有助于模型优化和部署。我们提供了多种预设配置:
# export_static_config.py
class StaticInputConfig:
# 批次大小配置
BATCH_SIZE = 1
# 序列长度配置
SEQ_LEN = 300 # 中等音频长度
# 特征维度配置
FEAT_DIM = 560
# 语言和文本规范化配置
LANGUAGE_ID = 0 # 中文
TEXTNORM_ID = 15 # 文本规范化
预设配置选项
- SHORT_AUDIO: 适合短音频快速推理 (SEQ_LEN=100)
- MEDIUM_AUDIO: 平衡性能和准确性 (SEQ_LEN=300)
- LONG_AUDIO: 适合长语音识别 (SEQ_LEN=512)
- BATCH_PROCESSING: 适合批量处理 (BATCH_SIZE=4)
2.2 静态导出实现
静态导出的核心是修改模型的forward方法,使其支持固定输入shape:
# export_sensevoice_static.py
def static_export_forward(self, speech, speech_lengths, language, textnorm, **kwargs):
"""
静态版本的导出forward方法,使用固定输入shape
"""
# 确保所有输入都在正确的设备上
device = speech.device
language = language.to(device)
textnorm = textnorm.to(device)
# 生成查询向量
language_query = self.embed(language).unsqueeze(1)
textnorm_query = self.embed(textnorm).unsqueeze(1)
# 拼接输入
speech = torch.cat((textnorm_query, speech), dim=1)
# 生成事件和情感查询
event_emo_query = self.embed(torch.LongTensor([[1, 2]]).to(device)).repeat(
speech.size(0), 1, 1
)
input_query = torch.cat((language_query, event_emo_query), dim=1)
speech = torch.cat((input_query, speech), dim=1)
# 更新语音长度
speech_lengths_new = speech_lengths + 4
# 通过编码器
encoder_out, encoder_out_lens = self.encoder(speech, speech_lengths_new)
if isinstance(encoder_out, tuple):
encoder_out = encoder_out[0]
# CTC输出
ctc_logits = self.ctc.ctc_lo(encoder_out)
return ctc_logits, encoder_out_lens
2.3 执行静态导出
# 使用中等音频预设导出模型
from export_sensevoice_static import export_static_model
export_static_model(
model_path="./models/ASR/SenseVoice-Small/",
output_path="./models/ASR/senseVoice-small-static",
preset="MEDIUM_AUDIO"
)
导出结果
导出后的模型信息:
============================================================
ONNX模型基本信息
============================================================
模型文件路径: ./models/ASR/senseVoice-small-static/model_static.onnx
ONNX版本: 7
生产者信息: pytorch 2.5.1
============================================================
模型输入信息 (共 4 个输入)
============================================================
Input 1: speech
数据类型: float32
形状: [1, 300, 560]
Input 2: speech_lengths
数据类型: int32
形状: [1]
Input 3: language
数据类型: int32
形状: [1]
Input 4: textnorm
数据类型: int32
形状: [1]
============================================================
模型输出信息 (共 2 个输出)
============================================================
Output 1: ctc_logits
数据类型: float32
形状: [1, 300, 25055]
Output 2: encoder_out_lens
数据类型: int32
形状: [1]
3. QNN模型编译
3.1 编译配置
编译过程需要指定输入规格和目标设备:
# compile.py
# 定义输入规格
input_spec = {
"speech": ((1, 300, 560), "float32"),
"speech_lengths": ((1,), "int32"),
"language": ((1,), "int32"),
"textnorm": ((1,), "int32"),
}
3.2 设备选择
自动选择合适的高通设备:
# 获取可用设备列表
available_devices = hub.get_devices()
# 尝试找到QCS8550相关的设备
device = None
for dev in available_devices:
if "qcs8550" in dev.name.lower() or "snapdragon" in dev.name.lower():
device = dev
break
# 如果没有找到QCS8550设备,使用第一个可用设备
if device is None:
device = available_devices[0]
3.3 提交编译任务
# 提交编译任务到Qualcomm AI Hub
compile_job = hub.submit_compile_job(
model=onnx_model_path,
input_specs=input_spec,
device=device,
name="sensevoice-small-qcs8550",
options="--target_runtime precompiled_qnn_onnx --output_names ctc_logits,encoder_out_lens"
)
# 等待编译完成
compile_job.wait()
# 检查编译状态
job_status = compile_job.get_status()
if job_status.code == 'FAILED':
print(f"编译失败: {job_status.message}")
else:
print("编译成功完成!")
3.4 性能分析
编译完成后可以进行性能分析:
# 下载编译后的模型
target_model = compile_job.get_target_model()
# 运行性能分析
profile_job = hub.submit_profile_job(
model=target_model,
device=device,
name="sensevoice-small-qcs8550"
)
profile_job.wait()
profile_status = profile_job.get_status()
4. 完整流程示例
4.1 一键导出和编译
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import os
import logging
from export_sensevoice_static import export_static_model
from compile import convert_model_to_qcs8550
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def complete_pipeline():
"""
完整的导出和编译流程
"""
# 步骤1: 导出静态模型
logger.info("=== 步骤1: 导出静态ONNX模型 ===")
try:
export_static_model(
model_path="./models/ASR/SenseVoice-Small/",
output_path="./models/ASR/senseVoice-small-static",
preset="MEDIUM_AUDIO"
)
logger.info("静态模型导出成功!")
except Exception as e:
logger.error(f"静态模型导出失败: {e}")
return False
# 步骤2: 编译QNN模型
logger.info("=== 步骤2: 编译QNN模型 ===")
static_model_path = "./models/ASR/senseVoice-small-static/model_static.onnx"
if not os.path.exists(static_model_path):
logger.error(f"静态模型文件不存在: {static_model_path}")
return False
try:
result_path = convert_model_to_qcs8550(
static_model_path,
output_dir="./outputs",
job_name="sensevoice-small-qcs8550",
output_name="sensevoice-small-qcs8550"
)
if result_path:
logger.info(f"QNN模型编译成功!结果保存在: {result_path}")
return True
else:
logger.error("QNN模型编译失败")
return False
except Exception as e:
logger.error(f"QNN模型编译过程中发生错误: {e}")
return False
if __name__ == "__main__":
success = complete_pipeline()
if success:
print("\n=== 流程完成 ===")
print("1. 静态ONNX模型已导出到: ./models/ASR/senseVoice-small-static/")
print("2. QNN模型已编译到: ./outputs/sensevoice-small-qcs8550/")
print("3. 模型可在高通QCS8550等硬件平台上运行")
else:
print("\n流程执行失败,请检查错误信息")
4.2 测试验证
# test_run.py
class StaticSenseVoiceTest:
"""
基于静态ONNX模型的SenseVoice测试类
"""
def __init__(self, model_dir: str = "./models/ASR/senseVoice-small-static"):
# 初始化模型
self.model_dir = Path(model_dir)
self.model_file = self.model_dir / "model_static.onnx"
# 加载配置和初始化组件
self.config = read_yaml(str(self.config_file))
self.tokenizer = SentencepiecesTokenizer(bpemodel=str(self.tokenizer_file))
self.frontend = WavFrontend(**self.config["frontend_conf"])
self.ort_infer = OrtInferSession(str(self.model_file), device_id="cpu", intra_op_num_threads=4)
# 模型参数
self.target_seq_len = 300
self.feat_dim = 560
def infer(self, feats: np.ndarray, feats_len: int, language: str = "auto", textnorm: str = "woitn") -> str:
"""
使用静态模型进行推理
"""
# 获取语言和文本规范化ID
lid = self._get_lid(language)
tnid = self._get_tnid(textnorm)
# 填充特征到固定长度 [300, 560]
padded_feats = self.pad_features(feats)
# 准备输入数据
speech_input = padded_feats.reshape(1, self.target_seq_len, self.feat_dim).astype(np.float32)
speech_lengths = np.array([min(feats_len, self.target_seq_len)], dtype=np.int32)
language_input = np.array([lid], dtype=np.int32)
textnorm_input = np.array([tnid], dtype=np.int32)
# 执行推理
outputs = self.ort_infer([speech_input, speech_lengths, language_input, textnorm_input])
ctc_logits, encoder_out_lens = outputs
# 处理输出并返回结果
return self._decode_output(ctc_logits, encoder_out_lens, feats_len)
5. 部署和使用
5.1 硬件要求
- 支持的高通芯片: QCS8550, QCS8450, QCS6490等
- 操作系统: Android, Linux
- 内存: 建议4GB以上
- 存储: 建议500MB以上可用空间
5.2 部署步骤
-
模型部署:
# 将编译后的模型文件复制到目标设备 cp -r ./outputs/sensevoice-small-qcs8550 /path/to/device/ -
依赖库安装:
# 在目标设备上安装Qualcomm SDK apt-get install qnn-sdk -
运行推理:
// C++示例代码 #include "QnnManager.h" // 初始化QNN运行时 QnnManager qnn_manager; qnn_manager.initialize("/path/to/model"); // 准备输入数据 std::vector<float> speech_input(1 * 300 * 560); std::vector<int32_t> speech_lengths = {300}; std::vector<int32_t> language = {0}; std::vector<int32_t> textnorm = {15}; // 执行推理 auto outputs = qnn_manager.infer({ {"speech", speech_input}, {"speech_lengths", speech_lengths}, {"language", language}, {"textnorm", textnorm} }); // 获取结果 auto ctc_logits = outputs["ctc_logits"]; auto encoder_out_lens = outputs["encoder_out_lens"];
5.3 性能优化建议
-
输入长度优化:
- 根据实际应用场景选择合适的SEQ_LEN
- 短音频使用SHORT_AUDIO预设,长音频使用LONG_AUDIO预设
-
批处理优化:
- 对于批量处理场景,使用BATCH_PROCESSING预设
- 合理设置BATCH_SIZE以平衡内存使用和吞吐量
-
硬件加速:
- 确保启用NPU加速
- 使用适当的精度设置(FP16/INT8)
-
内存管理:
- 及时释放不需要的内存
- 使用内存池减少分配开销
6. 故障排除
6.1 常见问题
问题1: 静态导出失败
错误: AttributeError: 'NoneType' object has no attribute 'modules'
解决方案: 检查模型路径是否正确,确保模型文件完整。
问题2: 编译超时
错误: 编译任务超时
解决方案: 增加编译超时时间,或者简化模型结构。
问题3: 设备不兼容
错误: 找不到合适的编译设备
解决方案: 检查设备列表,选择兼容的设备进行编译。
6.2 调试技巧
-
日志分析:
# 启用详细日志 logging.basicConfig(level=logging.DEBUG) -
模型验证:
# 验证ONNX模型 import onnx model = onnx.load("model_static.onnx") onnx.checker.check_model(model) -
输入输出检查:
# 检查模型输入输出 print(f"输入: {[inp.name for inp in model.graph.input]}") print(f"输出: {[out.name for out in model.graph.output]}")
模型编译结果


模型性能测试


7. 总结
本指南详细介绍了SenseVoice模型从静态导出到QNN编译的完整流程,包括:
- 环境准备: 安装必要的依赖和模型文件
- 静态导出: 使用固定输入shape导出ONNX模型
- QNN编译: 将ONNX模型编译为适配高通硬件的格式
- 部署使用: 在目标设备上部署和运行模型
- 性能优化: 提供优化建议和最佳实践
- 故障排除: 解决常见问题和调试技巧
通过遵循本指南,您可以成功地将SenseVoice模型部署到高通硬件平台,充分利用硬件加速能力,实现高效的语音识别功能。
附录
A.1 参考文档
A.2 代码仓库
A.3 许可证
- SenseVoice模型遵循Apache 2.0许可证
- Qualcomm AI Hub SDK遵循相应的许可证条款
联系方式
- 公众号:“CrazyNET”
- 邮箱:1145570610@qq.com
模型获取
- 公众号回复“voiceqnnonnx”
1626

被折叠的 条评论
为什么被折叠?



