☰
wenet 流式语音识别服务化加速全解析:基于 TensorRT 与 Triton 的 GPU 部署方案
2026/10/13 2:41:36 网站建设 项目流程
  • 人工智能
  • 语音
  • 深度学习
  • 预训练
  • 模型推理服务

【免费下载链接】wenet

Production First and Production Ready End-to-End Speech Recognition Toolkit

项目地址:https://gitcode.com/gh_mirrors/we/wenet
点击查看免费下载

wenet 提供了面向生产环境的端到端语音识别工具链。本文围绕 runtime/gpu/tensorrt/README.md 展开,完整讲解如何将流式 u2pp(Unified Two-Pass)Conformer 模型从 PyTorch 导出为 FP16 ONNX,再经图优化、TensorRT 引擎转换与 Triton Inference Server 的 Ensemble 编排,最终落地为一个低延迟、高并发的流式 ASR 服务。读完本文,你将掌握run_streaming_small_model.sh整条流水线中每个阶段的作用与参数含义,理解 LayerNorm 自定义插件、动态 shape 引擎、多 Profile 优化等关键技术的底层实现,并能复现 README 中记录的并发吞吐基准。

一、方案定位与整体思路

TensorRT 是 NVIDIA 推出的 GPU 推理优化引擎,通过算子融合、层间融合、精度校准(如 FP16)和内核自动调优等手段,显著提升神经网络在 GPU 上的推理效率。Triton Inference Server 则负责模型的部署管理、动态批处理、并发调度与序列状态管理。

在 wenet 的这套 GPU 部署方案中,两者各司其职:

  • TensorRT负责流式编码器(Encoder)的 FP16 推理,并通过自定义LayerNorm 插件绕开 ONNX 默认 LayerNorm 算子在 TensorRT 中的低效实现;
  • Triton通过 Python Backend、TensorRT Backend、ONNX Runtime Backend 与 Ensemble Scheduling 把「特征提取 → 编码 → CTC 前缀束搜索 → Attention 重打分」串成一条完整可服务的链路;
  • ONNX Runtime负责解码器(Decoder)的 Attention Rescoring 计算。

这套方案的全部脚本与模型仓库模板都位于 runtime/gpu/tensorrt 目录,一键入口是 run_streaming_small_model.sh。整体数据流为:

wav chunk → feature_extractor(FPBank) → encoder(TensorRT, FP16 + LayerNorm 插件) → wenet(CTC 前缀束搜索) → decoder(ONNX Runtime 重打分) → 文本

二、环境准备:Docker 镜像与依赖安装

README 推荐直接使用配套的 Triton Docker 镜像,省去手动安装 TensorRT、CUDA、Triton 的繁琐步骤。镜像内已预置 Triton Server 与 TensorRT 运行环境:

# 使用 runtime/gpu/Dockerfile/Dockerfile.server 对应的 docker 镜像 docker pull soar97/triton-wenet:22.12 docker run -it --rm --name "wenet_trt_test" --gpus all --shm-size 1g --net host soar97/triton-wenet:22.12

其中--gpus all将全部 GPU 透传给容器,--shm-size 1g增大共享内存以支撑 Triton 的序列批处理,--net host使用宿主机网络便于客户端直接访问服务端口。仓库自带的 Dockerfile.server 展示了镜像内部的构建思路:基于nvcr.io/nvidia/tritonserver:23.01-py3,安装swig、cmake、torch、kaldifeat(用于 FBank 特征提取)以及ctc_decoder(CTC 前缀束搜索的 SWIG 绑定)。

进入容器后,克隆 wenet 仓库并切换到 TensorRT 示例目录:

git clone https://github.com/wenet-e2e/wenet.git cd wenet/runtime/gpu/tensorrt

(若已在当前仓库环境下操作,直接进入 runtime/gpu/tensorrt 目录即可。)

安装 Python 依赖。脚本依赖 requirements.txt:

pip3 install nvidia-pyindex pip3 install -r requirements.txt

requirements.txt 中值得注意的几个关键依赖:

依赖版本/来源用途
tensorrt==8.5.1.7TensorRT Python API,用于构建/加载 engine
onnx_graphsurgeon>=0.3.21,来自 NGC PyPIONNX 图结构修改(LayerNorm 替换)
polygraphy无版本约束模型图精简(常量折叠)与在线检查
onnxruntime-gpu无版本约束解码器 rescoring 推理
cuda-python/onnxmltools无版本约束引擎测试与模型工具

若网络受限无法访问默认源,可先安装nvidia-pyindex为 NVIDIA 包提供索引,并为requirements.txt指定可用的 PyPI 镜像源后重试(README 中的提示原文即为此意)。

三、一键流水线:从 ONNX 到 Triton 服务

run_streaming_small_model.sh 是整个流程的编排脚本,以stage/stop_stage分段控制(默认stage=-1、stop_stage=4),可逐步或整体执行:

Stage职责关键产物
-1下载 Aishell2 u2pp Conformer 预训练模型并导出 FP16 流式 ONNXu2pp_aishell2_onnx/
0编译 LayerNorm CUDA 插件exp_streaming_trt/LayerNorm.so
1常量折叠 + 用插件替换 ONNX 中的 LayerNorm 子图encoderV2/V3/V4.onnx
2trtexec构建 FP16 动态 shape TensorRT engineencoder_fp16.plan
3用 convert.py 生成 Triton Model Repository 并拷入 engine 与插件model_repo_stateful_trt/
4启动tritonserver在线服务
5多 Profile 引擎导出与 ONNX/TensorRT 吞吐对比测试(注释中的可选阶段)基准数据

Stage -1:下载模型并导出 FP16 ONNX

脚本从公共地址下载 Aishell2 的20210618_u2pp_conformer_exp.tar.gz预训练模型,解压后调用 wenet/bin/export_onnx_gpu.py 导出流式 FP16 ONNX:

export PYTHONPATH=$PYTHONPATH:$(pwd) python3 wenet/bin/export_onnx_gpu.py \ --config=$model_dir/train.yaml \ --checkpoint=$model_dir/final.pt \ --cmvn_file=$model_dir/global_cmvn \ --ctc_weight=0.5 \ --output_onnx_dir=$onnx_model_dir \ --fp16 \ --streaming || exit 1 cp $model_dir/words.txt $model_dir/train.yaml $onnx_model_dir

对照 export_onnx_gpu.py 的参数定义,各参数含义如下:

参数默认值说明
--config必填训练配置文件(train.yaml),内含 encoder/decoder 结构、FBank 与 resample 等配置
--checkpoint必填模型权重文件final.pt
--cmvn_file""全局 CMVN 文件(流式编码器需要内嵌 global_cmvn)
--ctc_weight-1.0(回退到配置文件)两遍解码中 CTC 分数的权重,示例取0.5
--beam_size10束大小,同时决定 CTC top-k 输出宽度
--output_onnx_dironnx_modelONNX 模型输出目录
--fp16False是否导出 FP16 权重模型
--streamingFalse是否导出流式编码器(带 chunk + cache 输入)
--decoding_chunk_size16流式解码 chunk 大小
--num_decoding_left_chunks5允许的左向上下文 chunk 数

流式导出会产生encoder_fp16.onnx、decoder.onnx等文件,其中流式编码器的输入输出签名可以从脚本内的StreamingEncoder类(export_onnx_gpu.py)确认:输入为chunk_xs / chunk_lens / offset / att_cache / cnn_cache / cache_mask,输出为chunk_out / chunk_out_lens / ctc_log_probs / beam_log_probs / beam_log_probs_idx等——这正是后续trtexec动态 shape 与 Triton 配置中出现的全部张量名。

Stage 0:编译 LayerNorm 自定义插件

LayerNorm 在 ONNX 导出中会被展开成ReduceMean → Sub → Pow → ReduceMean → Add → Sqrt → Div → Mul → Add一长串小算子,TensorRT 逐算子推理效率不高。仓库在 LayerNormPlugin 目录提供了一个融合的自定义 CUDA 插件,将整段计算合并为一次规约加一次逐元素变换。

编译方式:

cd ./LayerNormPlugin make clean make cp LayerNorm.so $outputs_dir

Makefile 中通过GENCODE为多种 GPU 架构(sm_60/61/70/75/80/86)预编译 SASS,并链接cudart、cublas与nvinfer。从 LayerNormPlugin.cu 的 kernel 实现可以看到其核心思路:每个线程块用cub::BlockReduce一次规约出均值mu与平方和,随后计算rsigma = rsqrt(sum - mu*mu + 1e-6),再按gamma*(x-mu)*rsigma + beta写回;对 FP32 与 FP16 分别实例化了layerNormKernel<float,64,4>与layerNormKernel<half,32,8>,并通过REGISTER_TENSORRT_PLUGIN注册到 TensorRT 插件库,插件类型名为LayerNorm(见 LayerNormPlugin.h)。插件支持kFLOAT/kHALF两种精度且输出形状与输入一致。

该目录还附带 testLayerNormPlugin.py,可用于在 Python 侧加载LayerNorm.so、构建只含该插件的测试网络,并与 CPU 版 LayerNorm 结果逐元素对比验证(弱比较阈值1e-6),同时统计单次推理的平均耗时。

Stage 1:ONNX 图优化与 LayerNorm 替换

Stage 1 依次执行三步:

polygraphy surgeon sanitize $onnx_model_dir/encoder_fp16.onnx --fold-constant -o $outputs_dir/encoderV2.onnx python3 replace_layernorm.py --input_onnx $outputs_dir/encoderV2.onnx \ --output_onnx $outputs_dir/encoderV3.onnx \ || exit 1 polygraphy surgeon sanitize $outputs_dir/encoderV3.onnx --fold-constant -o $outputs_dir/encoderV4.onnx
  1. 用polygraphy surgeon sanitize --fold-constant做常量折叠,预先化简图中可离线计算的常量子图;
  2. 运行 replace_layernorm.py,用onnx_graphsurgeon匹配「ReduceMean → Sub → Pow → ReduceMean → Add → Sqrt → Div → Mul → Add」这一标准 LayerNorm 子图模式,把该子图折叠为一个自定义LayerNorm算子节点,并将原来独立的weight(gamma)与bias(beta)常量提取为插件的第二、第三个输入;脚本对输出端还有一个特殊处理:当 LayerNorm 的结果恰是图的输出chunk_out时,会在插件输出后接Squeeze并转为np.float16以匹配 FP16 引擎(replace_layernorm.py);
  3. 再次常量折叠,得到干净的encoderV4.onnx供引擎构建使用。

Stage 2:trtexec 构建 FP16 动态 shape TensorRT engine

Stage 2 使用 TensorRT 官方命令行工具trtexec构建引擎,同时启用 FP16 与动态 shape(Dynamic Shape),并挂载自定义插件:

MIN_BATCH=1 OPT_BATCH=16 MAX_BATCH=32 $trtexec \ --fp16 \ --onnx=$outputs_dir/encoderV4.onnx \ --minShapes=chunk_xs:${MIN_BATCH}x67x80,chunk_lens:${MIN_BATCH},offset:${MIN_BATCH}x1,att_cache:${MIN_BATCH}x12x4x80x128,cnn_cache:${MIN_BATCH}x12x256x7,cache_mask:${MIN_BATCH}x1x80 \ --optShapes=chunk_xs:${OPT_BATCH}x67x80,chunk_lens:${OPT_BATCH},offset:${OPT_BATCH}x1,att_cache:${OPT_BATCH}x12x4x80x128,cnn_cache:${OPT_BATCH}x12x256x7,cache_mask:${OPT_BATCH}x1x80 \ --maxShapes=chunk_xs:${MAX_BATCH}x67x80,chunk_lens:${MAX_BATCH},offset:${MAX_BATCH}x1,att_cache:${MAX_BATCH}x12x4x80x128,cnn_cache:${MAX_BATCH}x12x256x7,cache_mask:${MAX_BATCH}x1x80 \ --plugins=$outputs_dir/LayerNorm.so \ --saveEngine=$outputs_dir/encoder_fp16.plan

各输入张量含义(可与 export_streaming_conformer_trt.py 中构造测试输入的逻辑相互印证):

输入张量形状(以 batch 为例)含义
chunk_xsB × 67 × 80当前 chunk 的 FBank 特征,67 帧 × 80 维
chunk_lensB每个 chunk 的实际帧数
offsetB × 1全局帧偏移,保留 1 维以便 Triton 状态管理
att_cacheB × 12 × 4 × 80 × 12812 层、4 头注意力 KV 缓存,80 = cache_size,128 = d_k*2
cnn_cacheB × 12 × 256 × 712 层卷积模块缓存
cache_maskB × 1 × 80缓存掩码

这里67即解码窗口(decoding_window),80为 Mel 分箱数,二者均由 convert.py 从train.yaml的fbank_conf与流式 ONNX 配置计算得到。构建成功后得到encoder_fp16.plan,它就是 Triton 中 TensorRT Backend 实际加载的引擎文件。

Stage 3:生成 Triton Model Repository

Stage 3 调用 runtime/gpu/scripts/convert.py 生成完整的 Triton Model Repository:

python3 ../scripts/convert.py --config=$onnx_model_dir/train.yaml --vocab=$onnx_model_dir/words.txt \ --model_repo=$model_repo_path --onnx_model_dir=$onnx_model_dir cp $outputs_dir/encoder_fp16.plan $model_repo_path/encoder/1/ cp $outputs_dir/LayerNorm.so $model_repo_path/../

convert.py的核心逻辑是读取训练配置与 ONNX 导出配置,把model_repo_stateful_trt/下的config_template.pbtxt模板中的占位符(如#beam_size、#num_mel_bins、#frame_shift、#frame_length、#sample_rate、#output_size、#DTYPE、#cache_size、#chunk_size_in_seconds、#num_layers、#num_head、#att_cache_output_size、#cnn_module_cache、#decoding_window、#bidecoder等)替换为实际数值,逐个模型生成config.pbtxt,并把decoder.onnx/encoder_fp16.onnx拷入各模型目录(convert.py)。对于双向解码器(decoder以bi开头)会选择config_template2.pbtxt生成不带反向输入的解码器配置(convert.py)。

Stage 4:启动 Triton Server

LD_PRELOAD=./LayerNorm.so tritonserver --model-repository $model_repo_path \ --pinned-memory-pool-byte-size=2512000000 --cuda-memory-pool-byte-size=0:2024000000

这里的关键是LD_PRELOAD=./LayerNorm.so:TensorRT Backend 加载encoder_fp16.plan时需要反序列化其中的自定义LayerNorm插件,因此必须先把插件动态库预加载进进程,否则引擎加载会因找不到插件而失败。--pinned-memory-pool-byte-size与--cuda-memory-pool-byte-size分别控制主机端与 GPU 端的缓存池大小。

Stage 5:验证与吞吐测试

脚本中额外预留了 Stage 5(默认注释)用于三组验证:

  • 用 export_streaming_conformer_trt.py 以--test模式对多 Profile 引擎做端到端正确性测试;
  • 用trtexec --loadEngine ... | grep qps对单 Profile 引擎做batch=1/4/8/16/32的 QPS 扫描;
  • 用 benchmark_onnx_throughput.py 对encoder_fp16.onnx做batch_sizes=1,4,8,16,32,64,128,256的 ONNX Runtime 吞吐对照。

四、Triton Ensemble 模型编排详解

model_repo_stateful_trt 目录包含 5 个子模型,其中streaming_wenet是 Ensemble(集成调度)入口,其余为实际执行模型:

streaming_wenet (ensemble) ├── feature_extractor (python backend + kaldifeat FBank) ├── encoder (tensorrt backend, encoder_fp16.plan) ├── wenet (python backend + ctc_decoder 前缀束搜索) └── decoder (onnxruntime backend, attention rescoring)

各模型配置要点如下:

feature_extractor(config_template.pbtxt):Python Backend,输入原始wav与wav_lens,输出speech与speech_lengths。其实现 model.py 用kaldifeat.Fbank在 GPU 上提取 80 维 FBank;通过Feat类按corrid维护每条音频流的波形缓冲,按frame_stride(由chunk_size_s与frame_shift_ms计算)切帧,保证每个输出 chunk 恰好是解码窗口大小。默认 FBank 参数:frame_length_ms=25、frame_shift_ms=10、sample_rate=16000、chunk_size_s=0.64。

encoder(config_template.pbtxt):TensorRT Backend,default_model_filename: "encoder_fp16.plan",max_batch_size: 32。核心是sequence_batching中的state段:把offset、att_cache、cnn_cache、cache_mask四个输入声明为「序列状态」,由 Triton 自动维护上一 chunk 的缓存输出并作为下一 chunk 的输入,从而实现真正的无状态请求式流式推理——客户端无需自己管理缓存,只需持续提交音频 chunk。

wenet(config_template.pbtxt):Python Backend,负责 CTC 前缀束搜索与可选的 Attention 重打分编排。从参数模板可以看到生产级默认值:beam_size、cutoff_prob=0.9999、alpha=2、beta=1、rescoring=1,并可配置外部语言模型lm_path(通过Scorer注入)。其实现 model.py 用self.seq_states[corrid]记录每条流的前缀树(PathTrie)与 encoder 历史输出;wenet_onnx_model.py 中的batch_ctc_prefix_beam_search_cpu调用 SWIG 编译的ctc_beam_search_decoder_batch做多进程束搜索,batch_rescoring则在序列结束时把束内候选送入decoder模型重打分,最终经map_batch映射为文本。

decoder(config_template.pbtxt):ONNX Runtime Backend,输入encoder_out、encoder_out_lens、hyps_pad_sos_eos、hyps_lens_sos、r_hyps_pad_sos_eos、ctc_score(双向解码器时包含反向束),输出best_index,max_batch_size: 640,采用dynamic_batching聚合重打分请求。

streaming_wenet(config_template.pbtxt):Ensemble 模型,对外暴露WAV/WAV_LENS输入与TRANSCRIPTS输出,内部通过ensemble_scheduling将上述模型的输入输出张量按名映射串联,客户端只需请求这一个模型即可获得流式识别结果。

五、性能基准

README 给出了基于 Aishell1 测试集的真实基准:服务端使用 A10 GPU(16 vCPU / 60GB 内存),客户端为 4 vCPU / 16GB 内存,测试音频总时长 36108.919 秒,采用非模拟流式模式(non-simulate-streaming),即真正按 chunk 逐段推理。数据来源为 small u2pp ONNX 模型:

并发任务数处理时间(秒)
20(onnx fp16)123.796
40(onnx fp16)84.557
60(onnx fp16)73.232
80(onnx fp16)66.862
20(trt fp16 + layernorm plugin)90.582
40(trt fp16 + layernorm plugin)75.411
60(trt fp16 + layernorm plugin)69.602
80(trt fp16 + layernorm plugin)65.603

可以看到,在低并发(20 路)时 ONNX 与 TensorRT 方案差距明显(123.796s vs 90.582s),TensorRT 方案在 40 路以下并发下相对 ONNX 均有可观收益;并发升至 60~80 路时两者处理时间趋近,说明瓶颈逐渐从编码器算力转移到整体服务链路的其他环节(特征提取、CTC 搜索、批处理调度等)。这一结果同时印证了 LayerNorm 插件融合与 FP16 引擎对编码器推理效率的实际提升。

六、进阶:多 Profile 引擎与引擎直测

为了同时兼顾小 batch 的实时性与大 batch 的吞吐,仓库提供了 export_streaming_conformer_trt.py,可通过 Python API 构建包含两个优化 Profile的引擎。其用法示例如下:

export CUDA_VISIBLE_DEVICES="0" python3 export_streaming_conformer_trt.py \ --fp16 \ --onnxFile exp6_fp16/encoderV4.onnx \ --chunk_xs 1x67x80,32x67x80,64x67x80,1x67x80,4x67x80,8x67x80 \ --chunk_lens 1,32,64,1,4,8 \ --offset 1x1,32x1,64x1,1x1,4x1,8x1 \ --att_cache 1x12x4x80x128,32x12x4x80x128,64x12x4x80x128,1x12x4x80x128,4x12x4x80x128,8x12x4x80x128 \ --cnn_cache 1x12x256x7,32x12x256x7,64x12x256x7,1x12x256x7,4x12x256x7,8x12x256x7 \ --cache_mask 1x1x80,32x1x80,64x1x80,1x1x80,4x1x80,8x1x80 \ --plugin exp6_fp16/LayerNorm.so \ --trtFile exp6_fp16/encoder_test.plan \ --test

每个张量参数依次传入 6 个 shape(min/opt/max各 2 组),脚本会创建两个 optimization profile 分别对应两个 batch 区间(export_streaming_conformer_trt.py)。--test模式会在导出后自动以batch=1/4/8/16/32做正确性与延迟测试:先 5 次 warm-up,再统计 10 次推理的平均延迟、90/95/99 分位延迟与 QPS(get_latency_result)。当引擎存在两个 Profile 时,测试代码会以 batch 阈值(8)自动选择 Profile(test 函数),模拟服务端在大、小 batch 间切换 Profile 的调度策略。脚本还支持--useTimeCache将引擎构建时的 timing cache 序列化保存,二次构建时可复用缓存以大幅缩短调优耗时(export_streaming_conformer_trt.py)。

在run_streaming_small_model.sh的 Stage 5 中,这条多 Profile 导出路径以注释形式保留,便于按需启用:

python3 export_streaming_conformer_trt.py \ --fp16 \ --onnxFile $outputs_dir/encoderV4.onnx \ --chunk_xs ${MIN_BATCH1}x67x80,${OPT_BATCH1}x67x80,${MAX_BATCH1}x67x80,${MIN_BATCH2}x67x80,${OPT_BATCH2}x67x80,${MAX_BATCH2}x67x80 \ --chunk_lens ${MIN_BATCH1},${OPT_BATCH1},${MAX_BATCH1},${MIN_BATCH2},${OPT_BATCH2},${MAX_BATCH2} \ ... --plugin $outputs_dir/LayerNorm.so \ --trtFile $outputs_dir/encoder_fp16.plan \ --test || exit 1

七、总结

wenet 的 TensorRT + Triton 方案给出了一条清晰可复制的流式 ASR GPU 服务化路径:

  1. 导出:用 export_onnx_gpu.py 以--fp16 --streaming导出带缓存输入的流式编码器;
  2. 图优化:先polygraphy常量折叠,再用 replace_layernorm.py 把低效的 LayerNorm 子图替换为自研 LayerNormPlugin 融合算子;
  3. 引擎化:trtexec或 export_streaming_conformer_trt.py 构建 FP16 动态 shape(甚至多 Profile)engine;
  4. 服务化:convert.py 生成 Model Repository,Triton 通过sequence_batching的 state 机制自动维护流式缓存,Ensemble 把特征提取、编码、CTC 搜索、重打分串成单个对外模型;
  5. 验证:Stage 5 提供了插件数值校验、多 Profile 正确性测试与 ONNX/TensorRT 吞吐对照。

对于需要把 wenet 流式模型部署到 GPU 生产环境的开发者,可以直接以 run_streaming_small_model.sh 为模板,替换为自己训练的模型与配置,再结合本文对各阶段参数的解析按需调整 batch、Profile 与内存池大小,即可获得一套可复现、可扩展的高性能流式 ASR 服务。

  • 人工智能
  • 语音
  • 深度学习
  • 预训练
  • 模型推理服务

【免费下载链接】wenet

Production First and Production Ready End-to-End Speech Recognition Toolkit

项目地址:https://gitcode.com/gh_mirrors/we/wenet
点击查看免费下载

相关推荐

上一篇:你的数字足迹会说话:MouseTracks鼠标轨迹可视化工具深度解析
下一篇:如何用百灵快传实现手机电脑大文件秒传?局域网文件共享的3大创新方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询