news 2026/9/13 8:08:47

FunASR CPU 性能基准实战:Libtorch 推理、RTF 测试与 CER 评估全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FunASR CPU 性能基准实战:Libtorch 推理、RTF 测试与 CER 评估全解析

FunASR CPU 性能基准实战:Libtorch 推理、RTF 测试与 CER 评估全解析

【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR

本文围绕 runtime/docs/benchmark_libtorch.md 这一官方基准文档展开,系统讲解如何基于 Libtorch(PyTorch TorchScript)推理后端对 FunASR 的 Paraformer 离线识别模型做 CPU 性能基准测试:包括基准环境搭建、模型导出、并发 RTF(实时率)测量与 CER(字错误率)评测的完整流程。读完后,你可以直接复现文档中的测试配方(recipe),理解 RTF 与加速比的统计口径,并结合 runtime/python/libtorch 运行时源码弄清 Libtorch 推理链路的底层实现。

基准测试的总体配置

基准文档给出的测试配置如下:

  • 测试数据:Aishell1 test set,总语音时长36108.919 秒(约 10 小时)。
  • 测试模型:Paraformer-large(即模型仓库中的damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch),这是 16kHz 中文离线识别的旗舰模型。
  • 测试硬件:Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz,16 core - 32 processor,支持avx512_vnni指令集(这是 int8 量化加速的前提之一)。
  • 测试指标
    • processing time(s):整份测试集(或单个并发任务)的处理耗时;
    • RTF(Real-Time Factor):计算时间 / 语音时长,越小越快;
    • Speedup Rate(加速比):1 / RTF,即单位墙钟时间内能处理多少倍语时的音频。

整个基准由两份 shell 配方驱动:test_rtf.sh测速度,test_cer.sh测精度,两者共享同一套模型导出逻辑与 scp 切分工具,全部位于 runtime/python/utils 目录下。

环境安装与依赖准备

安装 ModelScope 与 FunASR

基准的第一步是安装推理框架本身:

pip install -U modelscope funasr # 国内用户也可以使用镜像源: # pip install -U funasr -i https://mirror.sjtu.edu.cn/pypi/web/simple

安装基准工具依赖

官方配方要求克隆仓库后,在基准工具目录安装额外依赖:

git clone <仓库地址> && cd FunASR cd funasr/runtime/python/utils pip install -r requirements.txt

对应的 requirements.txt 内容很短,共 5 个包,每个都有明确用途:

依赖作用
onnx模型导出为 ONNX 格式时使用(--type onnx
onnxruntimeONNX 后端推理(本基准配方中backend="onnx"的默认路径)
torch-quant >= 0.4.0TorchScript 模型量化,即 RTF 表格中 “torch int8” 一行的来源
funasr_torchLibtorch 运行时包,封装 TorchScript 模型的加载与推理
funasr_onnxONNX 运行时包,封装 ONNX 模型的加载与推理

如果不想从 PyPI 安装funasr_torch,也可以从仓库源码安装,见 runtime/python/libtorch/README.md:

cd funasr/runtime/python/libtorch pip install -e ./

需要注意两个前置条件,配方脚本对它们有隐含依赖:

  1. Perlsplit_scp.pl是 Perl 脚本,Linux 上一般默认自带;
  2. taskset:RTF 配方用taskset -c <core_id>把每个推理进程绑核,需要 Linux 环境。

模型导出(两条配方的共同 Stage 0)

两份配方在stage=0时都会先调用 FunASR 的导出模块把模型转换为运行时可用的形式:

python -m funasr.export.export_model \ --model-name ${model_name} \ --export-dir ${export_root} \ --type ${backend} \ --quantize ${quantize} \ --audio_in ${scp}

关键参数说明(以 test_rtf.sh 为准):

  • --model-name:模型标识,默认值damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch,支持直接传 ModelScope 模型名,也支持本地路径;
  • --export-dir:导出根目录,配方中为${export_root},导出产物位于${export_root}/${model_name}
  • --typetorch(导出 TorchScript,即 Libtorch 路径)或onnx
  • --quantizetrue时额外产出量化模型(Libtorch 路径下为model_quant.torchscript,由torch-quant完成 int8 量化);
  • test_cer.sh还多一个--fallback-num参数(默认 20),用于 ONNX 导出时指定回退到 Torch 算子执行的算子数,这解释了配方中fallback_op_num_torch变量的作用。

RTF 测试配方:test_rtf.sh 全解

配方参数

test_rtf.sh 的头部定义了全部可调项:

nj=32 # 并发任务数 stage=0 # 从哪个 stage 开始执行 scp="/path/to/aishell-1/data/test/wav.scp" # 测试集 wav.scp export_root="/path/to/export" # 模型导出根目录 split_scps_tool=split_scp.pl # scp 切分工具 rtf_tool=test_rtf.py # 单任务 RTF 测量脚本 model_name="damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch" backend="onnx" # "torch" # torch 即 Libtorch 路径 quantize='true' # 'False' tag=${model_name}/${backend}_quantize_${quantize} logs_outputs_dir=${export_root}/logs/${tag}/split$nj

使用时只需把scpexport_rootmodel_namebackendquantize改成自己的值,然后按文档启动:

nohup bash test_rtf.sh &> log.txt &

tag会拼出${backend}_quantize_${quantize},因此 torch/fp32、torch/int8、onnx/fp32、onnx/int8 四组实验的日志与导出目录天然隔离,互不覆盖。

Stage 1:切分、并发执行与结果汇总

stage=0完成导出后,stage=1的完整流程是:

  1. 切分perl split_scp.pl $scp wav.1.scp wav.2.scp ... wav.$nj.scp,把整份 wav.scp 均分为nj份。split_scp.pl 是经典的 Kaldi 风格脚本,无--utt2spk参数时按行数尽量均匀切分,并保证每个分片至少一行;

  2. 绑核并发:对每个JOB,执行

    core_id=$(expr $JOB - 1) taskset -c ${core_id} python test_rtf.py \ --backend ${backend} \ --model_dir ${model_dir} \ --wav_file ${logs_outputs_dir}/wav.${JOB}.scp \ --quantize ${quantize} &> ${logs_outputs_dir}/log.${JOB}.txt &

    每个进程独占一个核(taskset -c绑定到JOB-1号核),wait等待全部完成。这一步模拟了“单机多路并发推理”的真实服务场景——每路独立加载一份模型、各自处理自己那份音频;

  3. 汇总统计:脚本从每个log.$JOB.txt中 grep 出三个量并归并:

    # 每路任务计算耗时(最长一路) total_time_comput = max(各路 total_time_comput) # 全部音频时长之和 total_time_wav = sum(各路 total_time_wav) rtf = total_time_comput / total_time_wav speed = 1 / rtf

    最终打印:

    total_time_comput_ms: ... total_time_wav: ... total_rtf: ..., speech: ...

    这里有一个重要的统计口径:RTF 用的是“最慢一路的墙钟时间 / 全部音频总时长”,而不是各路 RTF 的简单平均。这正是并发场景下的正确口径——所有任务同时结束的那一刻才代表系统把整份数据“吐完”了。

单任务脚本 test_rtf.py 的实现细节

test_rtf.py 的逻辑与基准结果的可复现性直接相关,值得逐点说明:

model = Paraformer( args.model_dir, batch_size=1, quantize=args.quantize, intra_op_num_threads=args.intra_op_num_threads, )
  • backend == "torch"时导入的是 funasr.runtime.python.libtorch.funasr_torch 中的Paraformer类(Libtorch 运行时);backend == "onnx"时导入 ONNX 运行时的同名类。两者接口一致,因此同一份脚本可横向对比两种后端;
  • 先做30 次 warm-up(反复推理第一条音频并打印平均耗时与 RTF),剔除冷启动对计时的污染;
  • 随后对分配到的整个 scp 分片逐条推理,用time.time()前后差值记录总计算时间(毫秒);
  • 再用librosa逐条读取音频、按len(waveform)/16.0累加真实语音时长(注意这里按采样数直接计算,与数据集标注的 36108.919 秒口径一致);
  • 最终输出total_time_comput_mstotal_time_wav_mstotal_rtf(5 位小数),这三个字段就是 shell 脚本 grep 的目标。

--intra_op_num_threads(默认 1)是 ONNX Runtime 的线程参数;对 torch 后端无实际作用。由于每个进程已被taskset限制在单核,这里默认 1 线程与绑核策略是自洽的。

CER 测试配方:test_cer.sh 全解

test_cer.sh 与 RTF 配方结构同源,但增加了精度评测链路,由stage/stop_stage控制执行区间:

stage工作内容
0模型导出(含--fallback-num参数,见上文)
1scp 切分 +nj路绑核并发推理 + 结果合并为1best_recog/
2文本规整 + WER/CER 计算,tail -n 3打印结果

Stage 1 中,每路任务运行:

taskset -c ${core_id} python test_cer.py \ --backend ${backend} \ --model_dir ${model_dir} \ --wav_file ${output_dir}/wav.${JOB}.scp \ --quantize ${quantize} \ --output_dir ${output_dir}/${JOB} &> ${output_dir}/log.${JOB}.txt

test_cer.py 对分片内每条音频调用同一运行时(batch_size=1),把utt_id 识别文本utt_id 词元序列分别写入texttoken文件。全部任务完成后,脚本把njtext/token按 utt_id 排序合并到1best_recog/,保证评测集完整。

Stage 2 的两步处理:

python proce_text.py ${output_dir}/1best_recog/text ${output_dir}/1best_recog/text.proc python proce_text.py ${label_text} ${output_dir}/1best_recog/text.ref python compute_wer.py ${output_dir}/1best_recog/text.ref ${output_dir}/1best_recog/text.proc ${output_dir}/1best_recog/text.cer tail -n 3 ${output_dir}/1best_recog/text.cer
  • proce_text.py 做评测前规整:去除<s>/</s>/@@等特殊符号、去空格、转小写,并把中文按字拆开([x for x in text]),使后续编辑距离以“字”为粒度——所以中文场景下算出的就是 CER;

  • compute_wer.py 用动态规划编辑距离逐句统计ins/del/sub,汇总输出标准 Kaldi 风格结果:

    %WER <Err> [ <wrong_words> / <Wrd>, <Ins> ins, <Del> del, <Sub> sub ] %SER <S.Err> [ <wrong_sentences> / <Snt> ] Scored <N> sentences, <M> not present in hyp.

    每句还会写入text.cer明细文件(含 ref/hyp 对照),便于人工定位错例。Err = wrong_words * 100 / Wrd,即以参考字数为分母的字错误率。

这套流程的价值在于:换后端(torch vs onnx)、换精度(fp32 vs int8)后,CER 应当基本不变——它是量化与部署是否“精度无损”的验收标准,与 RTF 数据配合构成完整的部署前评估闭环。

官方基准结果与口径解读

基准文档在Intel(R) Xeon(R) Platinum 8269CY @ 2.50GHz(16core-32processor,avx512_vnni)上测得 Paraformer-large 的 RTF 结果如下(Aishell1 test set,36108.919 秒):

并发任务数处理耗时 (s)RTF加速比
1 (torch fp32)35220.097610.3
1 (torch int8)17460.048420.7
32 (torch fp32)2360.0066152.7
32 (torch int8)1140.0032317.4
64 (torch fp32)2350.0065153.7
64 (torch int8)1130.0031319.2

从这组数据可以读出三个结论:

  1. int8 量化收益接近 2 倍:单任务下 RTF 从 0.0976 降到 0.0484,几乎精确减半;并发场景下同样约 2 倍。这对应导出链路的quantize=truemodel_quant.torchscript,依赖torch-quant完成;
  2. 并发扩展非常线性:1 路 3522s → 32 路 236s,单核 32 路并发约等效 150 倍吞吐(相对单路约 15 倍,考虑到 8269CY 有 32 逻辑核,接近理想扩展);
  3. 32 路与 64 路吞吐趋于饱和:64 路(235s/113s)并未比 32 路更快,说明在 32 逻辑核的机器上并发数超过核数后收益消失,nj建议与可用核数对齐。

Libtorch 运行时的 README 还给出了一段单条 5.53s 音频、100 次平均的跨后端对比(Intel Xeon Platinum 8163 @ 2.50GHz):

后端RTF (FP32)
PyTorch(训练态框架)0.110
Libtorch(TorchScript)0.048
ONNX Runtime0.038

可以看出:同样的模型权重,从 eager PyTorch 换到 TorchScript 推理,RTF 降到约 0.44 倍;再换 ONNX 又略降。三者中 Libtorch 处于训练框架与 ONNX 之间,优势在于与 PyTorch 生态(包括torch-quant量化)无缝衔接,且量化后 RTF 可进一步减半,这也是基准文档标题直接以 Libtorch 命名的原因。

Libtorch 运行时的源码级实现

基准配方最终调用的是 runtime/python/libtorch/funasr_torch/paraformer_bin.py 中的Paraformer类,结合源码可以弄清几个关键行为:

1. 模型加载与自动导出。构造函数中(paraformer_bin.py#L53-L66):

model_file = os.path.join(model_dir, "model.torchscript") if quantize: model_file = os.path.join(model_dir, "model_quant.torchscript") if not os.path.exists(model_file): # 自动调用 AutoModel(...).export(type="torchscript", quantize=quantize)

即:quantize=True时加载model_quant.torchscript,否则加载model.torchscript;若目录里还没有 TorchScript 产物,会自动用funasr.AutoModel完成导出。若model_dir不是一个存在的本地路径,还会先尝试modelscope.hub.snapshot_download自动下载(paraformer_bin.py#L37-L51)。所以test_rtf.py里直接传model_dir就能工作——目录里必须有model.torchscript(或model_quant.torchscript)、config.yamlam.mvntokens.json四类文件,这与 README 对导出产物的描述一致。

2. 特征提取链路extract_feat(paraformer_bin.py#L188-L200)走WavFrontend.fbanklfr_cmvn:Fbank 特征加上 LFR(低帧率融合)与 CMVN(均值方差归一化,参数来自am.mvn),再 pad 成float32张量与int32长度张量,喂给torch.jit.load载入的脚本模型——输入输出就是feats, feats_len → am_scores, valid_token_lens

3. 解码与时间戳。非自回归解码由decode(am_scores, valid_token_lens)一步完成(Paraformer 本身是平行解码结构,这是其快于自回归 Transformer 的根本原因);若导出模型额外返回us_alphas/us_peaks(BiCif 分支输出),运行时还会调用time_stamp_lfr6_onnx生成字级时间戳并做句级后处理,最终返回{"preds", "timestamp", "raw_tokens"}结构(paraformer_bin.py#L91-L143)。

4. 输入形态__call__接受str路径、np.ndarray波形或List[str]三种输入,内部统一经librosa.load重采样到 16kHz 后按batch_size分批推理;device_id-1(默认)时走 CPU,正数索引时把特征搬到 CUDA——同一份代码同时覆盖 CPU 基准与 GPU 部署。

复现基准的注意事项与前提

基于文档与源码,复现时需要注意以下前提与限制:

  1. 路径必须替换test_rtf.sh/test_cer.shscplabel_textexport_root指向作者内网的 NFS 路径(如/nfs/...),复现前必须替换为本地的 Aishell1 test set 路径(wav.scp 与 text 标签文件);
  2. 并发数与核数匹配nj路推理通过taskset绑到0..nj-1号核,nj不要超过物理核数,否则 64 路数据所示的饱和现象会拖慢总耗时;
  3. RTF 口径:总 RTF = max(各路耗时) / Σ(各路语音时长),与单条音频的 RTF 定义不同,横向对比时保持nj一致;
  4. 量化依赖 avx512_vnni:表格中 int8 收益依赖 CPU 支持 avx512_vnni;在不支持的 CPU 上量化未必有 2 倍收益,建议 fp32/int8 都测一轮;
  5. CER 与 RTF 是两条独立结论:RTF 回答“能跑多快”,CER(stage 2 的%WER输出)回答“量化/换后端后精度是否保持”,部署决策需要两者同时满足。

小结

runtime/docs/benchmark_libtorch.md定义的这套基准,用 Aishell1 整段测试集、split_scp.pl切分 +taskset绑核并发的配方,把“模型导出 → 并发推理计时 → RTF/加速比统计 → 文本合并 → 字级编辑距离评测”串成了可直接执行的 shell 流水线,工具链集中在 runtime/python/utils(test_rtf.pytest_cer.pycompute_wer.pyproce_text.pysplit_scp.pl),推理核心则位于 runtime/python/libtorch 的funasr_torch包。官方数据显示:在带 avx512_vnni 的 Xeon 8269CY 上,Paraformer-large 经 Libtorch + int8 量化后,32 路并发下整段 Aishell1 test set 的处理 RTF 达到 0.0032(加速比约 317 倍),且并发扩展到核数附近仍保持近似线性——这套配方与结论,正是 CPU 端 ASR 服务容量规划与后端选型(fp32/int8、torch/onnx)的直接依据。

【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 8:08:01

Elasticsearch 写入链路优化:Bulk 批量、Refresh 策略与写入吞吐调优

Elasticsearch 写入链路优化&#xff1a;Bulk 批量、Refresh 策略与写入吞吐调优 Elasticsearch 作为一款强大的搜索引擎&#xff0c;其写入性能往往成为整个系统的瓶颈。本文将深入探讨 Elasticsearch 写入链路优化的三个关键方面&#xff1a;Bulk 批量操作、Refresh 策略调整…

作者头像 李华
网站建设 2026/9/13 8:07:59

多模态推理架构落地:端侧部署与端云协同的四大关键方向

2025年我做技术评审时&#xff0c;几乎每一场研讨都会争同一个问题&#xff1a;多模态推理到底应该放在哪一端&#xff1f;云端算力充分&#xff0c;但延迟和隐私兜不住&#xff1b;端侧响应快&#xff0c;可模型一上视觉就发热、掉电、内存爆掉。争论到最后&#xff0c;经常变…

作者头像 李华
网站建设 2026/9/13 8:06:22

无广告AI对话平台的商业价值与技术实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 8:06:15

多模型融合与贝叶斯优化在时间序列预测中的应用

1. 项目概述&#xff1a;多模型融合的贝叶斯优化预测方案这个项目本质上是在解决一个经典的时间序列预测问题——如何利用历史多变量数据准确预测未来值。我们采用了三种不同的深度学习模型架构&#xff08;CNN-BiLSTM、BiLSTM以及它们的贝叶斯优化版本&#xff09;&#xff0c…

作者头像 李华