- 人工智能
- 语音
- 深度学习
- 预训练
- 模型推理服务
【免费下载链接】wenet
Production First and Production Ready End-to-End Speech Recognition Toolkit
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.txtrequirements.txt 中值得注意的几个关键依赖:
| 依赖 | 版本/来源 | 用途 |
|---|---|---|
tensorrt | ==8.5.1.7 | TensorRT Python API,用于构建/加载 engine |
onnx_graphsurgeon | >=0.3.21,来自 NGC PyPI | ONNX 图结构修改(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 流式 ONNX | u2pp_aishell2_onnx/ |
| 0 | 编译 LayerNorm CUDA 插件 | exp_streaming_trt/LayerNorm.so |
| 1 | 常量折叠 + 用插件替换 ONNX 中的 LayerNorm 子图 | encoderV2/V3/V4.onnx |
| 2 | trtexec构建 FP16 动态 shape TensorRT engine | encoder_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_size | 10 | 束大小,同时决定 CTC top-k 输出宽度 |
--output_onnx_dir | onnx_model | ONNX 模型输出目录 |
--fp16 | False | 是否导出 FP16 权重模型 |
--streaming | False | 是否导出流式编码器(带 chunk + cache 输入) |
--decoding_chunk_size | 16 | 流式解码 chunk 大小 |
--num_decoding_left_chunks | 5 | 允许的左向上下文 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_dirMakefile 中通过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- 用
polygraphy surgeon sanitize --fold-constant做常量折叠,预先化简图中可离线计算的常量子图; - 运行 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); - 再次常量折叠,得到干净的
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_xs | B × 67 × 80 | 当前 chunk 的 FBank 特征,67 帧 × 80 维 |
chunk_lens | B | 每个 chunk 的实际帧数 |
offset | B × 1 | 全局帧偏移,保留 1 维以便 Triton 状态管理 |
att_cache | B × 12 × 4 × 80 × 128 | 12 层、4 头注意力 KV 缓存,80 = cache_size,128 = d_k*2 |
cnn_cache | B × 12 × 256 × 7 | 12 层卷积模块缓存 |
cache_mask | B × 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 服务化路径:
- 导出:用 export_onnx_gpu.py 以
--fp16 --streaming导出带缓存输入的流式编码器; - 图优化:先
polygraphy常量折叠,再用 replace_layernorm.py 把低效的 LayerNorm 子图替换为自研 LayerNormPlugin 融合算子; - 引擎化:
trtexec或 export_streaming_conformer_trt.py 构建 FP16 动态 shape(甚至多 Profile)engine; - 服务化:convert.py 生成 Model Repository,Triton 通过
sequence_batching的 state 机制自动维护流式缓存,Ensemble 把特征提取、编码、CTC 搜索、重打分串成单个对外模型; - 验证: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
相关推荐
WeNet GPU加速部署终极指南:TensorRT和Triton推理服务器配置教程
WeNet GPU加速部署终极指南:TensorRT和Triton推理服务器配置教程 WeNet是一款面向生产环境的端到端语音识别工具包(Production
人工智能语音深度学习预训练模型推理服务CosyVoice 加速部署实战:基于 NVIDIA Triton 推理服务器与 TensorRT-LLM 的三套解决方案
CosyVoice 加速部署实战:基于 NVIDIA Triton 推理服务器与 TensorRT LLM 的三套解决方案 本文是 CosyVoice 仓库 r
语音音频人工智能大模型微调预训练模型推理服务CosyVoice 基于 DiT Token2Wav 的 Triton + TensorRT-LLM 服务化加速全指南
CosyVoice 基于 DiT Token2Wav 的 Triton + TensorRT LLM 服务化加速全指南 本指南以 CosyVoice 仓库 ru
语音音频人工智能大模型微调预训练模型推理服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考