PaddleSpeech 基于 SpeechX 的 C++ ASR 部署指南:U2/U2++/DeepSpeech2 模型 FP32 与 INT8 推理实战
2026/9/23 2:53:25 网站建设 项目流程

PaddleSpeech 基于 SpeechX 的 C++ ASR 部署指南:U2/U2++/DeepSpeech2 模型 FP32 与 INT8 推理实战

【免费下载链接】PaddleSpeechEasy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleSpeech

导读

本文基于 PaddleSpeech 仓库中的 ASR 部署示例(demos/asr_deployment/README_cn.md),系统讲解如何利用 SpeechX 运行时对 U2 / U2++ / DeepSpeech2 等主流 ASR 模型进行 C++ 端部署。文中覆盖从环境搭建、SpeechX 编译、模型与测试数据准备,到 FP32 / INT8 双精度解码的完整操作流程,并结合 runtime 目录下的引擎源码与示例脚本,深入剖析recognizer_main的流式分块推理机制、核心命令行参数含义,以及 CER / RTF 指标的计算口径。读完本文,你将能够独立在 Linux 环境(如 Ubuntu 16.04 + Docker)中跑通一套端到端的 C++ ASR 离线/流式识别管线,并理解量化部署带来的精度与性能权衡。

一、简介:为什么用 C++ 部署 ASR

在工业实践中,语音识别模型上线时往往需要脱离 Python 训练框架、以更高的吞吐和更低的延迟运行。SpeechX 是 PaddleSpeech 提供的 C++ 推理运行时,本部署方案通过它实现对U2、U2++、DeepSpeech2三类经典 ASR 模型的 C++ 部署:

  • U2:统一流式与非流式(Unified Streaming and Non-streaming)两阶段模型,兼顾 CTC 前缀束搜索的流式输出与 Attention Rescoring 的精度修正;
  • U2++:U2 的增强版,引入双向(left-to-right / right-to-left)解码器,重打分(rescoring)精度更高,是当前仓库部署示例的主角;
  • DeepSpeech2:经典的端到端语音识别模型,同样支持该运行时部署。

从源码结构看,C++ 推理引擎整体位于 runtime/engine/asr,包含nnet(神经网络前向)、decoder(解码器)、recognizer(识别器控制器)与server(WebSocket 服务)四大子模块;对应的可运行示例位于 runtime/examples/u2pp_ol/wenetspeech,示例采用wenetspeech 上训练的 U2++ Chunk Conformer 静态模型在 AISHELL-1 测试集上评测。

二、环境准备

部署示例对编译与运行环境有明确要求,原文档列出的环境如下:

组件版本要求
Python3.7
Docker 镜像registry.baidubce.com/paddlepaddle/paddle:2.2.2-gpu-cuda10.2-cudnn7
操作系统Ubuntu 16.04.7 LTS
编译器gcc / g++ / gfortran 8.2.0
CMake3.16.0

几点实操建议:

  • Docker 镜像中已内置 CUDA 10.2 / cuDNN 7 与对应版本的 PaddlePaddle 运行时,run.sh在编译阶段依赖PADDLE_LIB_PATH定位 Paddle 动态库(见 runtime/examples/u2pp_ol/wenetspeech/path.sh 中被注释的paddle.sysconfig.get_include()逻辑),因此建议直接在上述 Docker 容器内进行编译与运行,避免宿主环境库版本冲突;
  • 若 CPU 指令集支持avx512_vnni,INT8 量化模型的推理会获得额外加速(本文第五节的结果即在该类 CPU 上测得);
  • gfortran 8.2.0 是编译 Kaldi 风格特征前端(如 OpenBLAS、线性代数依赖)所必需的,请确保与 gcc 版本一致。

三、编译 SpeechX 运行时

示例脚本run.sh在首次运行时若检测不到SPEECHX_BUILD目录(即编译产物),会自动进入SPEECHX_ROOT执行bash build.sh完成整仓编译(对应 runtime/examples/u2pp_ol/wenetspeech/run.sh):

if [ ! -d ${SPEECHX_BUILD} ]; then pushd ${SPEECHX_ROOT} bash build.sh popd fi

编译完成后,产物默认位于runtime/build/Linux/x86_64/engine/asr下,并通过path.sh将以下关键二进制目录加入PATH(见 path.sh):

  • nnet:神经网络前向相关 bin(如u2_nnet_mainu2_onnx_nnet);
  • decoder:CTC 前缀束搜索 / TLG 解码器 bin;
  • recognizer:识别器主程序recognizer_main
  • kaldi/fstbinkaldi/lmbin:WFST 解码与语言模型工具;
  • frontend/audio:音频与特征前端工具。

整个引擎的 CMake 组织可参看 runtime/engine/asr/CMakeLists.txt,其依赖(openblas、openfst、kenlm、paddleinference、fastdeploy 等)由 runtime/cmake 下的脚本统一管理。

四、U2++ 识别部署完整实操

以下步骤对应原文档第三节「例子」,实际操作目录为 runtime/examples/u2pp_ol/wenetspeech。

4.1 初始化环境变量

source path.sh

该脚本会校验编译产物是否存在,并将recognizer_main等 bin 与LD_LIBRARY_PATH配置好(PADDLE_LIB_PATH需在容器内由 Paddle 环境自动注入)。

4.2 下载模型、准备测试数据与 cmvn

run.sh --stage 0 --stop_stage 1

注意:原文档此处写作--stage 0 --stop_stage 1,而当前仓库中run.sh的阶段划分已调整为 stage 0 负责「下载模型与数据」、stage 1 与 stage 2 分别执行 FP32 与 INT8 解码(见 run.sh),建议以仓库脚本实际为准。stage 0 将自动完成:

  • 下载FP32 静态模型asr1_chunk_conformer_u2pp_wenetspeech_static_1.3.0.model.tar.gz,解压后得到export.jit(Paddle 静态图模型)、mean_std.json(cmvn 均值方差文件)与unit.txt(词表/符号表);
  • 下载INT8 量化模型asr1_chunk_conformer_u2pp_wenetspeech_static_quant_1.3.0.model.tar.gz
  • 准备两个测试输入:单条中文测试音频zh.wav(生成wav.scp)与 AISHELL-1 测试集aishell_test.zip(生成aishell_test.scp,每条utt_id + wav 绝对路径)。

scp文件是 Kaldi 风格的输入描述格式,例如:

BAC009S0764W0121 /workspace/PaddleSpeech/runtime/examples/u2pp_ol/wenetspeech/data/test/S0764/BAC009S0764W0121.wav BAC009S0764W0122 /workspace/PaddleSpeech/runtime/examples/u2pp_ol/wenetspeech/data/test/S0764/BAC009S0764W0122.wav

如需识别单个音频,只需构造形如key path/to/wav/file的两列 scp 即可(详见 runtime/examples/u2pp_ol/wenetspeech/README.md)。

4.3 解码:FP32 与 INT8

# FP32 ./local/recognizer.sh # INT8 ./local/recognizer_quant.sh

两个脚本结构完全一致(recognizer.sh 与 recognizer_quant.sh),差异仅在于:

  • 模型目录:FP32 指向asr1_chunk_conformer_u2pp_wenetspeech_static_1.3.0.model/,INT8 指向asr1_chunk_conformer_u2pp_wenetspeech_static_quant_1.3.0.model/
  • INT8 脚本的--model_path直接传模型目录名export(Paddle 量化导出产物),FP32 传export.jit
  • 日志与结果文件分别写到recognizer.log/recognizer.quant.log,结果汇总到exp/aishell_recognizerexp/aishell.recognizer.quant.rsl

脚本内部以nj=20并行分片(utils/run.pl JOB=1:$nj),每个 JOB 调用一次recognizer_main,最终用 utils/compute-wer.py 以--char=1(字符级)计算 CER 并打印尾部统计。

4.4 解码输出解读

原文档给出了单条音频的典型输出,这里结合源码解释每一类日志(对应 recognizer_main.cc):

I1026 16:13:24.683531 48038 u2_recognizer_main.cc:55] utt: BAC009S0916W0495 I1026 16:13:24.683578 48038 u2_recognizer_main.cc:56] wav dur: 4.17119 sec. I1026 16:13:24.683595 48038 u2_recognizer_main.cc:64] wav len (sample): 66739 I1026 16:13:25.037652 48038 u2_recognizer_main.cc:87] Pratial result: 3 这令 I1026 16:13:25.222124 48038 u2_recognizer_main.cc:87] Pratial result: 5 这令被贷款 I1026 16:13:25.608129 48038 u2_recognizer_main.cc:87] Pratial result: 9 这令被贷款的员工们请 I1026 16:13:25.804101 48038 feature_cache.h:44] set finished I1026 16:13:25.804128 48038 feature_cache.h:51] compute last feats done. I1026 16:13:26.246963 48038 u2_recognizer_main.cc:113] BAC009S0916W0495 这令被贷款的员工们请食难安
  • utt / wav dur / wav len:读取到测试语句 ID、音频时长(秒)与采样点数;
  • Pratial result: N ...:流式解码过程中的部分结果N为已累积的帧/分块编号),体现 U2++ 的流式输出能力——边接收音频边给出中间识别文本;
  • feature_cache.h: set finished / compute last feats done:整段音频输入完毕后,特征缓存完成收尾计算(对应 runtime/engine/asr/nnet/nnet_producer.cc 中的生产者-消费者缓存);
  • 最后一行utt + 识别文本:经过Attention Rescoring重打分后的最终结果,由result_writer写入ark,t格式结果文件。

4.5 主程序的流式推理原理

从 recognizer_main.cc 可以看出 C++ 部署的流式处理骨架:

  • 默认--streaming_chunk=0.36(秒)、--sample_rate=16000,因此每块音频为0.36 × 16000 = 5760个采样点;
  • 主循环把 wav 按块切分,逐块调用recognizer_ptr->Accept(wav_chunk)送入特征前端(fbank 提取),全部送入后调用SetInputFinished()并等待解码线程结束(nnet_producer.h 中的 Accept / Reset 接口即此缓存队列);
  • 随后执行AttentionRescoring(),用解码器得分与 CTC 得分加权融合得到最终结果;
  • 结束时统计total wav durationtotal decode cost并打印RTF is: ...,其中RTF = 总解码耗时 / 总音频时长(含提特征与解码,更贴近端到端口径)。

五、核心参数详解

recognizer_main的关键参数定义集中在 runtime/engine/asr/decoder/param.h 与 runtime/engine/asr/recognizer/recognizer_main.cc。下表为示例脚本中实际使用的参数及其源码注释含义:

参数示例值默认值源码注释/作用
--use_fbanktruefalse使用 fbank 特征(false 表示线性特征)
--num_bins80161Mel 滤波器组个数
--cmvn_filemean_std.json""cmvn 均值-方差文件路径
--model_pathexport.jit/exportavg_1.jit.pdmodelPaddle 静态图模型路径
--word_symbol_tableunit.txt""词/字符号表
--nnet_decoder_chunk161神经网络前向解码分块数
--receptive_field_length77两个 CNN(kernel=3) 下采样模块的感受野长度
--subsampling_rate44两个 CNN(kernel=3) 模块的下采样率
--wav_rspecifierscp:...""测试音频 rspecifier
--result_wspecifierark,t:...""识别结果 wspecifier

5.1 解码分块参数与感受野

--nnet_decoder_chunk--receptive_field_length--subsampling_rate三者共同决定模型前向的输入窗口大小与步长。在 ctc_prefix_beam_search_decoder_main.cc 中可看到如下计算:

int32 chunk_size = FLAGS_receptive_field_length + (FLAGS_nnet_decoder_chunk - 1) * FLAGS_subsampling_rate; int32 chunk_stride = FLAGS_subsampling_rate * FLAGS_nnet_decoder_chunk;

即:一次前向所需帧数 = 感受野长度 +(分块数 - 1)× 下采样率;相邻两次前向的帧步长 = 下采样率 × 分块数。以示例取值(7, 16, 4)为例:chunk_size = 7 + 15 × 4 = 67 帧,chunk_stride = 64 帧——这正是 Chunk Conformer 流式模型中「当前块 + 左侧缓存上下文」的典型配置,保证流式解码的因果性。subsampling_rate还会被U2Nnet读取模型属性覆盖(见 u2_nnet.cc)。

5.2 其他可调解码参数

param.h中还定义了示例脚本未显式传入、使用默认值的解码器参数,按需可追加到命令行:

  • --acoustic_scale=1.0:声学分数权重;
  • --beam=15.0--lattice_beam=7.5--max_active=7500:束搜索规模控制;
  • --ctc_weight=0.5--rescoring_weight=1.0:CTC 得分与重打分得分的融合权重(ctc_prefix_beam_search_score.h中实现);
  • --reverse_weight=0.3:仅当使用双向 Transformer 解码器时该值 > 0 才启用从右到左解码(U2++ 即属于此情形);
  • --num_left_chunks=-1:解码时可用的左侧块数,-1 表示不限制;
  • --blank_threshold=0.98:blank 跳过阈值。

5.3 特征输入模式

除直接解码 wav 外,示例还支持「特征输入」模式:feat.sh负责将mean_std.json转换为 Kaldi 格式的 cmvn 并抽取 fbank 特征,之后可走recognizer_main--feature_rspecifier输入(详见 runtime/examples/u2pp_ol/wenetspeech/README.md)。--use_fbank--num_bins等参数即在该模式下决定特征口径。

六、部署结果:CER 与 RTF 对比

原文档在「结果」一节给出了测试口径:CER 在aishell-test上计算,RTF 包含提特征与解码(更端到端)。仓库中的完整评测数据见 runtime/examples/u2pp_ol/wenetspeech/RESULTS.md。

6.1 FP32 推理

原文档(测试机器 Intel Xeon Gold 6271C)记录:

Overall -> 5.75 % N=104765 C=99035 S=5587 D=143 I=294 Mandarin -> 5.75 % N=104762 C=99035 S=5584 D=143 I=294 English -> 0.00 % N=0 C=0 S=0 D=0 I=0 Other -> 100.00 % N=3 C=0 S=3 D=0 I=0
RTF is: 0.315337

RESULTS.md 在 Intel Xeon Gold 6148(同样支持 avx512_vnni)上测得 FP32 RTF 为0.265234(总时长 36108.9 秒 / 总耗时 9577.31 秒)。CER 统计行中的N/C/S/D/I含义为:N总字符数、C正确、S替换、D删除、I插入,CER = (S + D + I) / N。

6.2 INT8 量化推理

原文档记录 INT8 结果(CER 5.87% 与 5.83% 在不同版本文档中略有出入,以仓库 RESULTS.md 为准):

Overall -> 5.83 % N=104765 C=98943 S=5675 D=147 I=286 Mandarin -> 5.83 % N=104762 C=98943 S=5672 D=147 I=286 English -> 0.00 % N=0 C=0 S=0 D=0 I=0 Other -> 100.00 % N=3 C=0 S=3 D=0 I=0
RTF is: 0.269674

INT8 相比 FP32:CER 从 5.75% 略升至 5.83%(约 +0.08 个百分点),精度损失极小;而 RTF 在支持avx512_vnni的机器上有望获得可观加速(原文档 6271C 机器上 FP32 RTF 0.315337 → INT8 RTF 0.269674,提速约 14%)。这说明在延迟敏感、对精度容错较高的工业场景中,INT8 量化是性价比很高的部署选项。

6.3 附加:TLG 解码(无 Attention Rescoring)

RESULTS.md还提供了recognizer_wfst.sh(TLG 词图解码、不做 Attention Rescoring)的参考结果:CER 4.73%(N=104765,C=100001,S=4283,D=481,I=187),RTF 0.283。该路径对应ctc_tlg_decoder(见 runtime/engine/asr/decoder/ctc_tlg_decoder.cc),适合结合外部语言模型与 WFST 做工业级解码方案。

七、总结

本文完整走通了 PaddleSpeech 中「SpeechX C++ ASR 部署」的实战链路:

  1. 部署对象:U2 / U2++ / DeepSpeech2 模型,以 wenetspeech 训练的 U2++ Chunk Conformer 静态模型为示例;
  2. 运行方式:Ubuntu 16.04 + Paddle 2.2.2 Docker 环境内编译 runtime 引擎,通过 run.sh 一键完成模型下载、数据准备与 FP32/INT8 解码;
  3. 原理支撑:recognizer_main.cc 展示了 0.36s 分块的流式推理 + Attention Rescoring 两阶段解码;param.h 定义了感受野、下采样率、束宽、CTC/重打分权重等全部关键参数;
  4. 效果衡量:AISHELL-1 测试集上 FP32 CER 5.75%、RTF 0.265~0.315;INT8 CER 5.83%、RTF 0.270,量化方案在近乎无损精度下显著提升解码速度。

对于希望进一步扩展的读者,runtime/examples/u2pp_ol 下还提供了 FastDeploy(ONNX)与 WFST 解码等变体脚本,可直接在示例目录中参考对应local/*.sh的使用方式。

【免费下载链接】PaddleSpeechEasy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleSpeech

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

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

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

立即咨询