2026-10-27 · 深度笔记

语音识别服务部署实录:模型大小、实时率和并发

接 ASR 到线上踩过的坑:接口返回 200 但正文为空、端到端延迟由分片大小决定、长音频显存爆掉,以及怎么给并发定上限。

云主机AI工程语音识别部署

第一次把语音识别接到线上,用户上传一段录音,接口返回 200,正文是空字符串。

排查了半小时,最后发现是 WAV 头里声明的采样率和实际数据对不上,解码出来全是 0。这次之后我养成了一个习惯:ASR 的第一个问题永远是"音频本身对不对",不是模型好不好。

三个必须先定下来的量

部署之前,我会先把三个数写在纸上:模型档位、实时率、并发。这三个数定了,机器规格才有依据。

实时率(RTF) 是最核心的指标,定义是"处理耗时 / 音频时长"。RTF 小于 1 才能比实时快。给一段 60 秒音频,模型花了 20 秒处理完,RTF 就是 0.33。

档位显存量级GPU 上 RTF中文识别体感
tiny1G 以内0.02 - 0.05专有名词基本认不出
base1-2G0.04 - 0.08日常对话够用,口音重会崩
small2-4G0.08 - 0.15多数场景够用
medium5-6G0.15 - 0.25稳定
large8-10G0.3 - 0.6最稳,显存也最吃紧

RTF 这几个区间是我在自己那台卡上量到的量级,不同框架的量化方式和批处理策略会让数字差不少,别当精确值。

反直觉:用户不在乎字错率,在乎一次过

我一开始的优化目标是压低 WER(字错率),为此直接上了最大的模型。

上线之后看反馈,用户抱怨的不是"识别错了几个字",而是"经常要重录"。有个做口播的用户说得直白:错三个字我改一下就行,返工二十分钟我受不了。

于是我做了对比实验,同样的模型让用户手动改那几个错字和重录,多数人选了改字。

结论是:降 WER 的边际收益远低于降低"一次通过率"。所以我反过来把资源投在噪声抑制和 VAD(语音活动检测)上,让输入更干净,模型档位反而降了一档。

坑一:端到端延迟由分片大小决定,不由模型决定

这是流式 ASR 最反直觉的一点。

同样用 small 模型,我把流式分片从 1 秒改成 0.5 秒,端到端延迟几乎没变;但从 1 秒改成 3 秒,延迟直接涨了一大截。

原因是流式识别的等待时间结构:

端到端延迟 ≈ 分片长度 + 分片长度 × RTF

模型再快,分片不开完你也拿不到文字。第一句话要等一个完整分片出来才开始显示,这段固定开销跟模型无关。

所以流式场景的分片大小是个用户体验的直接旋钮:

分片首字延迟尾部精度我的建议
0.5s低差,断句容易断错只在强实时场景用
1-2s中中通用默认
3-5s高好追求准确率时用
整段上传最高最好离线转写用

补充一个细节:分片边界要带重叠,否则一个词正好被切在两片中间,两半都识别不出来。我用 200ms 重叠,代价是 20% 额外算力,换边界词基本不丢。

坑二:长音频会把显存吃穿

用户传一段一小时的会议录音,我直接整段丢给模型,显存爆了。

大模型做转写有两种方式:非自回归的整段编码,或者自回归的逐帧解码。前者对音频长度是线性显存,后者更糟,注意力随长度平方增长。

我现在的做法是:先用 VAD 把音频切成语义完整的片段,每段上限 30 秒,段间留 1 秒重叠。

ffmpeg -i input.m4a -ar 16000 -ac 1 -c:a pcm_s16le \
  -af "highpass=f=100,lowpass=f=7800,silencedetect=n=-45dB:d=0.6" \
  -f wav output.wav

这一行命令里有四个值得注意的地方:

切段之后再送模型,单段 30 秒的显存占用是可控的,并发数也可以稳定提升。

坑三:拼接处出现重复字

切段解决了显存,但带来了新问题:相邻两段都有重叠,拼接后经常出现"今天今天我们讨论"或者半个词重复。

处理办法是在拼接时做重叠区去重。我用最朴素的办法——把重叠部分的文本用最长公共前缀裁掉:

def merge_with_overlap(prev: str, cur: str, overlap_chars: int = 20) -> str:
    a, b = prev[-overlap_chars:], cur[:overlap_chars]
    for k in range(overlap_chars, 0, -1):
        if a[-k:] == b[:k]:
            return prev + cur[k:]
    return prev + cur

这个函数不完美——中文里重复片段可能正好是真的重复。但它比不处理强,而且逻辑简单,出错时一眼能看出来。

更稳的做法是把切分点交给 VAD 而不是固定时长,让切分落在静音处,就不会切在词中间。我后来两个都用,VAD 负责主要切分,固定 30 秒只作兜底上限。

并发上限怎么定

并发不是"显存装得下模型就能并发多少"。显存要同时装:模型权重 + 批处理中的样本 + KV cache。

给个粗算的起点:

可并发条数 ≈ (总显存 - 权重占用 - 预留 2G) / 单条推理峰值显存

单条推理峰值显存受两个东西影响:音频长度和是否开 beam search。开了 beam search,beam 宽度每翻一倍,显存占用也跟着翻。

我最后的选择是 medium 模型 + 不开 beam + 30 秒切段 + 动态批处理,把并发控制交给框架调度器,而不是自己在应用层排队。应用层排队反而容易堆积出难看的尾延迟。

监控上盯三个数就够:队列等待时长、RTF 的 P95、显存占用率。只有第一个经常是瓶颈——GPU 算力大部分时间是闲的,闲在等请求。

各家的显卡规格、实例配额和计费方式一直在变,文章里的量级只用来做判断,实际配置请以官方页面为准。

接着读