Ascend-SACT/WeNet
模型介绍文件和版本Pull Requests讨论分析
下载使用量0

适配环境信息

NPU硬件:A2/910B

操作系统:ARM

部署方式:单卡

推理框架:Triton Inference Server

模型介绍

WeNet 是一个开源的端到端语音识别框架,旨在通过简化传统语音识别系统的复杂性,提供高效、灵活且准确的语音识别解决方案。它结合了最新的深度学习技术,广泛应用于学术研究和工业场景。

一、编译适配昇腾的Triton Server

方案一:自行编译TritonServer

  1. 选取一个含有CANN、torch npu的基础镜像,这里选用了mindie镜像(正式版本可能缺cmake、git等工具,自行安装);
  2. 安装Triton Server需要的工具库;
apt update;
apt install zlib1g-dev;
apt install openssl;
apt install libssl-dev;
apt install libxml2-dev;
apt install libcurl4-openssl-dev;
apt install libarchive-dev;
pip install distro --trusted-host pypi.org --trusted-host files.pythonhosted.org
  1. 下载Triton Server源码,切换分支
git clone https://github.com/triton-inference-server/server.git
cd server
git checkout r24.12
  1. 修改build脚本:参考下面的修改源码中的build.py文件,因为是torch_npu作推理后端,选用Python backend和客户需要的ensemble backend,可以把其他backend和gpu相关的注释掉,避免下载过多依赖和编译问题。
if target_platform() != "windows":
        all_backends = [
            "ensemble",
            #"identity",
            #"square",
            #"repeat",
            #"tensorflow",
            #"onnxruntime",
            "python",
            #"dali",
            #"pytorch",
            #"openvino",
            #"fil",
            #"tensorrt",
        ]
  1. 执行build脚本编译:运行build.py脚本,会自动下载和编译依赖仓,编译完成后同目录下会生成build文件夹(编译时间较长),需要将build/opt下的文件拷贝到环境/opt目录下。
./build.py -v --enable-all --no-container-build --build-dir='pwd'/build
cp -r build/opt/tritonserver /opt
  1. 启动Triton Server 启动Triton server命令如下,MODEL_DIR表示适配好的model repository路径。 /opt/tritonserver/bin/tritonserver --model-repository=${MODEL_DIR}

方案二:在TritonServer镜像中安装依赖

  1. 拉取tritonserver:24.01-py3官方镜像,并启动容器;
  2. 再安装CANN、fst、torch_npu等依赖;
  3. 参考文档基于tritonserver python backend在昇腾上部署模型服务化。

二、WeNet模型转om

获取wenet预训练权重wenet-e2e/wenet-github

获取WeNet om转换脚本和离线推理脚本 wenet-modelzoo

  1. 转非流式onnx modelzoo的源码需要修改: export_onnx_npu.py修改点:558行添加 configs["is_json_cmvn"]= True    (若train.yaml中‘is_json_cmvn’字段不为True需自行修改)
export PYTHONPATH=./wenet    #wenet源码路径
python3 export_onnx_npu.py --config ./train.yaml --checkpoint ./final.pt --output_onnx_dir ./onnx/ --num_decoding_left_chunks 4 --reverse_weight 0.3 --cmvn_file ./train/global_cmvn

转换成功后得到offline_encoder.onnx 和 decoder.onnx 2. encoder转非流式动态分档om

export batch_size=1
atc --input_format=ND --framework=5 --model=/disk1/liangliang/wenet/onnx_model/onnx/offline_encoder.onnx --input_shape="speech:${batch_size},-1,80;speech_lengths:${batch_size}" --dynamic_dims="262;326;390;454;518;582;646;710;774;838;902;966;1028;1284;1478" --output=/disk1/liangliang/om-wenet/offline_encoder_static_bs${batch_size} --log=error --soc_version=Ascend910B3
  1. decoder转om 修改脚本中的onnx路径和om ouput路径 bash static_decoder.sh
### static_decoder.sh
batch_size=1
SOC_VERSION=Ascend910B3

atc --model=./onnx/decoder.onnx --framework=5 --output=./om-wenet/offline_decoder_static_${batch_size} --input_format=ND \
--input_shape="encoder_out:${batch_size},-1,256;encoder_out_lens:${batch_size};hyps_pad_sos_eos:${batch_size},10,-1;hyps_lens_sos:${batch_size},10;ctc_score:${batch_size},10" --log=error \
--dynamic_dims="96,5;96,6;96,7;96,8;96,9;96,10;96,11;96,12;96,13;96,14;96,15;96,16;96,17;96,18;96,19;96,20;144,5;\
144,6;144,7;144,8;144,9;144,10;144,11;144,12;144,13;144,14;144,15;144,16;144,17;144,18;144,19;144,20;\
144,21;144,22;144,23;144,24;144,25;144,26;144,27;144,28;144,29;384,5;384,6;384,7;384,8;384,9;384,10;384,11;\
384,12;384,13;384,14;384,15;384,16;384,17;384,18;384,19;384,20;384,21;384,22;384,23;384,24;384,25;384,26;\
384,27;384,28;384,29;384,30;384,31;384,32;384,33;384,34;384,35;384,36;384,37;384,38;384,39;384,40;384,41;\
384,42;384,43;384,44;" \
--soc_version=${SOC_VERSION}
  1. 使用om进行离线推理 安装ais_bench、ctc_decoder
  • (1)安装ais_bench推理包

ais_bench下载链接和文档

aisbench api guide

通过以上链接下载whl包,用于离线安装,aclruntime根据python版本选择对应whl包;

#安装aclruntime
pip3 install ./aclruntime-0.0.2-cp311-cp311-linux_aarch64.whl
#安装ais_bench推理程序
pip3 install ./ais_bench-0.0.2-py3-none-any.whl
  • (2)ctc_decoer(若不需要ctc解码则不用安装)
git clone https://github.com/Slyne/ctc_decoder.git
apt-get update
apt-get install swig
apt-get install python3-dev
cd ctc_decoder/swig && bash setup.sh
  • (3)recognize_om离线推理(依赖ctc_decoder)
export batch_size=1
export ASCEND_RT_VISIBLE_DEVICES=0
export PYTHONPATH=./wenet
python3 recognize_om.py --config=./train.yaml --test_data=./aishell/s0/data/test/data.list --dict=onnx_model/lang_char.txt --mode=attention_rescoring --result_file=static_result.log --encoder_om=./offline_encoder_static_bs1.om --decoder_om=./offline_decoder_static_1.om --batch_size=1 --device_id=0 --static --test_file=static_test_result.txt --num_process=8 --encoder_gears="262, 326, 390, 454, 518, 582, 646, 710, 774, 838, 902, 966, 1028, 1284, 1478" --decoder_gears="96, 144, 384"

使用aishell数据集测试离线性能:

进程数decode类型耗时推理效率(s/s)npu利用率
16ctc_greedy19189788%
16attention_rescoring29124370%
  • 性能分析: 离线推理效率较高,使用wenet.dataset + torch.Dataloader多进程在CPU并行提取特征;这种离线音频特征提取方法,在24进程下每秒可处理约2000s音频频谱。

三、Triton Server服务化

  1. model_repo目录结构 triton server推理需要一个model_repo路径作为入参,此路径下包含config.pbtxt配置文件(包含模型的输入输出type/dims、模型路径、资源限制等);用于定义模型推理逻辑model.py,它的主要作用是封装模型的加载、预处理、推理计算、后处理等完整流程,使 Triton服务器能够通过Python代码调用模型并处理推理请求。
model_repository/
└── model/
├── 1/
│ └── model.py
└── config.pbtxt
  1. config.pbtxt的文件结构
name: "asr_om_model"
backend: "python"
# 输入配置(WAV文件路径列表)
input [
  {
    name: "wav_paths"
    data_type: TYPE_STRING
    dims: [-1]  # 动态维度,支持批量输入多个WAV路径
  }
]
# 输出配置(ASR识别结果列表)
output [
  {
    name: "asr_results"
    data_type: TYPE_STRING
    dims: [-1] 
  }
]
# 模型参数配置(需与model.py中的参数解析对应)
parameters [
  # 必选参数:OM模型路径
  {
    key: "encoder_om_path"
    value: { string_value: "./om-wenet/offline_encoder_static_bs1.om" }  # 替换为实际encoder OM路径
  },
......
]
# 资源限制(根据实际硬件调整)
instance_group [
  {
    count: 8  # server实例数量
    kind: KIND_CPU  # Python后端选择CPU,实际特征提取和推理调用NPU
  }
]
  1. model.py推理文件适配
import triton_python_backend_utils as pb_utils
# 导入特征提取依赖
import torch
import torchaudio
import torchaudio.compliance.kaldi as kaldi
import torch_npu
from ais_bench.infer.interface import InferSession
import ...
def get_dict(dict_path: str) -> tuple[List[str], Dict[int, str]]:
    """加载字符字典"""
    
def preprocess_audio(audio_path: str, device_id: int = 0) -> tuple[np.ndarray, np.ndarray, float]:
    # 音频文件特征提取
 
def _extract_feats_multi_thread(wav_paths: List[str], device_id: int, feature_workers: int) -> List[tuple]:
    # 音频文件多线程特征提取(选用)
    
class TritonPythonModel:
    def initialize(self, args: Dict):
        """初始化1个OM实例"""
        
    def _parse_parameters(self) -> Dict:
        """参数解析(必选/可选参数校验)"""
        
    def _static_shape_pad(self, feats: np.ndarray, feats_lengths: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
        """静态形状填充:适配OM输入要求,动态dims"""
        
    def _decode_ctc_greedy(self, beam_log_probs_idx: np.ndarray, encoder_out_lens: np.ndarray) -> str:
        """CTC贪心解码:基于char_dict映射"""
        
    def execute(self, requests):
        """推理核心:1. 解析输入:wav_paths  2.调用_preprocess_audio  3. 单OM实例推理  4. 构造Triton响应"""
        
    def finalize(self):
        """模型销毁:仅输出日志(OM实例由InferSession自动释放)"""
  1. fbank适配npu
  • (1)当前fbank使用的torchaudio或者kaldifeat作特征提取,这些库只原生适配cpu/cuda,在cpu上运行较慢;
  • (2)尝试直接将waveform.to(torch.device),把音频数据放到npu上,再执行torchaudio.kaldi.fbank(),有aclnnabs算子报错,入参类型不支持;
  • (3)通过修改torchaudio.compliance.kaldi,替换掉fft函数,用复数的实部和虚部计算模长来计算;
# 修改核心点:
# spectrum = torch.fft.rfft(strided_input).abs()                                  #原先的abs算子不适配,有输入类型报错
real_view = torch.view_as_real(torch.fft.rfft(strided_input))                     #替换
spectrum = torch.sqrt(real_view[..., 0].pow(2) + real_view[..., 1].pow(2))
  • .abs() 直接计算复数的模(幅度),等价于 sqrt(实部² + 虚部²)。
  • torch.view_as_real(complex_tensor) 将复数张量转换为实数表示,形状从 (..., n) 变为 (..., n, 2),其中最后一个维度的 0 表示实部,1 表示虚部。
  • 手动计算幅度:通过分离实部和虚部,分别平方后求和,再开平方根,结果与原代码的 .abs() 完全一致。
  • (4)torchaudio具体修改步骤:
1、将patch放到torchaudio的路径下,如果路径不一致请自行修改
pip show torchaudio
cp fbank_npu.patch /usr/local/lib/python3.11/site-packages/torchaudio/

2、在torchaudio路径下执行补丁
patch -p1 < fbank_npu.patch
  1. 修改model.py

使用方法修改:在kaldi.fbank方法前,将waveform转到npu上,生成得到speech后再转到cpu上;

waveform = waveform.to(torch.device("npu:0"))
······
speech = features.unsqueeze(0).cpu().numpy()
  1. Triton server拉起命令:
export PYTHONPATH=/xxx/wenet   #需要声明wenet源码的路径
/opt/tritonserver/bin/tritonserver --model-repository=/xxxxx/asr_repo --http-port=8083 --grpc-port=8084 --metrics-port=8085    #model-repository要设为wenet_om_model的上层文件夹
  1. 模拟client进行性能测试

image.png

用aishell  7176条测试集进行测试,decoder选择ctc_greedy:

场景并发耗时(s)推理效率(s/s)
1实例20093388
8实例20015.42341
12实例20011.53135

测试对比发现,在单卡12实例时推理吞吐最大、效率最高,相比优化前性能明显。

  • 客户侧修改测试后,在未使用OM推理的情况下,性能提升2-3倍,P95时延下降50%。

QA:

(1)Q:报错 NPU function error: at_npu::native::AclSetCompileopt(aclCompileOpt::ACL_PRECISION_MODE, precision_mode), error code is 500001 

A:triton启动时如果遇到以上报错,通过source /usr/local/Ascend/ascend-toolkit/set_env.sh解决;

(2)Q:多实例启动时,推理节点显示:unhealth;stub process is unhealthy and it will be restarted

A:观察运行内存是否打满,如果是内存不足导致的则增加内存。