Skip to main content

03 - 听:音频怎么变成文字

上一篇把用户那四秒录音切成了 200 个向量。现在要让它变成「我要一杯少冰的珍珠奶茶」,11 个字。

200 个向量对 11 个字。你手上的训练数据是几万条这样的录音,每条配一句文字 —— 注意,只配了整句。没有任何一条数据告诉你「第 37 个向量属于第 3 个字」。

你想训练模型说「看到这个向量就输出这个字」,可根本不知道每个向量对应哪个字。这种输入和输出数量对不上、又没人标出对应关系的情况,有个名字叫对齐(alignment)。

绕开它有三种办法,业界这三十年就在这三条路上来回走 —— 而且三条现在都还活着,各占各的场子。

一、对齐问题

假设你用最直白的办法:给每个向量配一个字。200 个向量、11 个字,那就得有 189 个向量对应「什么都不输出」—— 而训练数据没告诉你是哪 189 个。猜错了,模型学到的就是错的对应。

人工标注能解决,但标一小时语音要几十小时人工,训练却需要几万小时。成本上走不通。

所以三条路线是在回答同一个问题的两半:训练时怎么在没标注的情况下学推理时怎么知道该吐字了

上排每格 = 一个编码器向量(20 ms),下排 = 目标文字。中间的连线没人给输入250 格这一段是「今」?还是「今天」?还是包含了前面的静音?训练数据里没有一条说得清输出12 字250 对 12,而且是多对一 —— 平均每个字摊到 20 格,但实际每个字长短不一,有的字后面还跟着停顿。人工标注能解决,但标一小时语音要几十小时人工,而训练需要几万小时。这条路从成本上就走不通。三条技术路线,本质上都在回答同一个问题:怎么在没有这些连线的情况下把模型训出来。
三条路线的分歧点就在中间那一层:CTC 允许每格输出一个「空白」再把重复折叠掉,等于枚举所有可能的连法;AED 干脆不画连线,让解码器每出一个字就回头扫一遍整排格子;Transducer 规定每步只能「出一个字」或「往右挪一格」,让连线天然单调不回头。

这三条路线各有一个流传下来的缩写,先把名字拆开 —— 它们的名字本身就说明了各自的做法

缩写全称中文名字在说什么
CTCConnectionist Temporal Classification连接主义时序分类「时序分类」=给时间轴上每一帧做一次分类。「连接主义」是上世纪对神经网络的旧称,属于历史包袱。所以字面意思就是「用神经网络逐帧分类」,它的创新在于额外发明了一个「空白」类别
AEDAttention-based Encoder-Decoder基于注意力的编码器-解码器名字就是结构本身:一个编码器把整段音频读成向量,一个解码器逐字往外吐,靠注意力机制自己决定每出一个字要回头看哪几帧
Transducer
(旧称 RNN-T)
Recurrent Neural Network Transducer循环神经网络转换器transducer 在信号处理里指「把一种信号转成另一种的装置」,比如换能器。这里指把声学序列转成文字序列。前面的 RNN 是历史包袱 —— 现在的实现骨干早就不是循环网络了,但名字沿用至今

三条路线不是替代关系,现在都还活着:CTC 至今仍是最省的关键词唤醒方案,Transducer 仍是手机端流式听写的主力,AED 则演化成了今天的 Audio LLM。

CTCAED(Whisper 系)Transducer
怎么绕开对齐枚举所有能折叠成目标句的路径求和不显式对齐,靠交叉注意力自己找限定每步只能出字或听下一帧
代价帧与帧条件独立,没有内部语言模型要听完整段才能开口,天生不流式三个网络,工程复杂

三条路线的具体差别:

CTCAED(Whisper 系)Transducer(RNN-T)
结构编码器 + 每帧一个分类头编码器 + 自回归解码器编码器 + 预测网络 + 联合网络
解码步数等于帧数,一次并行算完等于输出字数,自回归帧数 + 字数,交替推进
天然流式
内部语言模型有(解码器就是)有(预测网络)
典型失败同音字选错、漏字静音段幻觉、循环重复删除错误偏多
现在还用在哪唤醒词、强制对齐、蒸馏出的轻量模型通用离线转写、多语言端侧和电话流式听写
静音段幻觉是 AED 的固有性质,不是 bug

AED 的解码器本身就是一个完整的语言模型。即使编码器那边什么都没送过来,它照样能自己往下编 —— 表现出来就是一段没人说话的音频被转写成一句通顺的话,有时甚至是训练数据里高频出现的字幕组署名。

改不掉,因为这就是结构决定的。工程上只能靠前置 VAD(Voice Activity Detection,语音活动检测 —— 一个只判断「这段有没有人在说话」的轻量模型)把静音段直接掐掉,不让它进模型。

二、Whisper 的三十秒定长窗口

Whisper 是 AED 路线最有影响力的实现,它的很多工程约束都源自一个设计决定:编码器的输入长度是固定的 30 秒

whisper/audio.py 里的四个常数决定了整条链路重采样 16 kHzSAMPLE_RATE补齐 / 截断到 30 秒N_SAMPLES = 480000log-mel 3000 帧hop 160 即 10 ms/帧两层 stride-2 卷积一个 token 覆盖 20 ms1500 个编码器状态恒定长度,与实际语音无关定长带来的好处形状恒定,编码器可以整个捕成 CUDA Graph 直接回放批大小是唯一变量,按 1/2/4/8 分桶就能覆盖全部情况显存占用可精确预估,不会因为来了段长音频突然 OOMSGLang-Omni 默认就开着编码器图捕获,按 batch 分桶定长带来的代价3 秒的音频也要按 30 秒算编码器,浪费九成算力超过 30 秒必须切块,切在字中间就会丢字或重复补零段是模型没见过的分布,是静音幻觉的高发区短音频高并发场景,编码器会先于解码器成为瓶颈这也是为什么后来的 Audio LLM 大多放弃定长窗口,改成变长编码 —— 但代价是失去了上面左栏的三条好处,要另外想办法把编码器成本压回去。
「定长 vs 变长」在服务端是个真实的两难。SGLang-Omni 的做法是两边都留:Whisper 路径吃定长的红利做图捕获,Qwen3-ASR 路径则用应用层切块(默认 30 秒一块)把变长问题转回定长问题。

长音频切块这件事,SGLang-Omni 的 Qwen3-ASR 路径给了一组值得抄的默认值:模型本身能吃 1200 秒的单次输入,但服务端默认只送 30 秒一块(max_audio_clip_s),理由是短块的批处理效率更高,而且输出 token 预算会随片长自己膨胀;单个请求最多 8 块并发(max_concurrent_chunks),防止一个长上传把别人的请求挤光;整体上限 3600 秒,因为解码后的波形要整段留在内存里。还有一条容易忽略的:尾块短于 0.5 秒时,会把前一刀往前挪来把尾巴吸收掉 —— 模型对过短输入的行为不可靠。

三、编码器接大语言模型

Qwen3-ASR、Fun-ASR、MOSS-Transcribe-Diarize 走的都是同一个结构,可以概括为「把 AED 的解码器换成一个真正的大语言模型」。

MOSS-Transcribe-Diarize 的实际结构,其他 Audio LLM 大同小异80 维 log-mel100 帧/秒Whisper 编码器24 层 · d=10244× 时间合并每 4 帧拼成一个投影 MLP4096 → 1024连续向量,落在 LLM 的嵌入空间里替换提示词中的 audio_pad 占位符Prefill:Qwen3 一次并行前向建好 KV cache90 分钟音频编码后可达数万 token,靠分块预填充切成 4096 一块分块期间要抑制流式输出,否则会吐出中间态Decode:自回归吐文本输出里可以直接带说话人标签和起止时间戳转写、分离、断句在同一次生成里完成换成 LLM 之后多出来的四件事① 热词与领域词表可以直接写进提示词,不用改模型 —— 人名、公司名、产品术语的识别率靠这个救② ��语言提示变成一段文本后缀,指定语言和自动检测走同一条路;③ 说话人分离不再需要单独的聚类流程④ 整条链路复用 LLM 推理的全部基建:连续批处理、KV cache、CUDA Graph、分块预填充,一样不落
④ 是它能在这两年迅速取代传统 ASR 栈的真正原因。传统 ASR 引擎的批处理、显存管理、调度都是自成一套的;换成 LLM 骨干后,过去五年推理框架积累的所有优化立刻可以复用,SGLang-Omni 把 ASR 和 TTS 放进同一个调度器就是这个逻辑的直接结果。

官方给了这条链路的两张图。一张是流水线视角,从波形到 mel 到编码器到时间合并到投影,最后进 LLM:

MOSS-Transcribe-Diarize 的 ASR 流水线:波形经 80 维 mel 与 24 层 Whisper 编码器,4 倍时间合并后过 Adaptor MLP 变成 1024 维连续向量,再进 Qwen3 做预填充与自回归解码
出处:sglang-omni docs/_static/image/moss-td-asr-pipeline.svg。中间那格 0.23 −1.47 0.89 2.01 ··· 是重点:交给 LLM 的是浮点向量,不是 token id。

另一张是模型视角,来自模型自己的仓库:

MOSS-Transcribe-Diarize 的模型结构图
出处:OpenMOSS-Team/MOSS-Transcribe-Diarize 模型卡。它支持 128K 上下文、约 90 分钟音频,转写、说话人分配、时间戳在同一次生成里完成。

代价也要说清楚:一个 1.7B 的 Audio LLM 干的是过去 300M 参数模型的活。它换来的是多语言、热词、分离、时间戳这些能力打包在一起,但如果你的场景只是单语言、固定领域的短语音转写,传统 CTC 模型在同样硬件上能快一个量级,而且不会幻觉。不该用 Audio LLM 的判据很清晰:输出格式固定、领域封闭、对幻觉零容忍的场景(比如语音指令、车载唤醒、电话号码识别)。

四、流式识别与前瞻延迟

离线识别可以等用户说完再出结果,流式不行 —— 它要边说边出字。

矛盾在于:要听准当前这个字,模型得先听见它后面那一小段。汉语里「四」和「是」,光听那一个音节分不出来,得靠后面接的字。可「先听见后面」就意味着要等,而等待就是延迟。

这段为了听准而多等的音频,有个名字叫前瞻(look-ahead)。它有多长,基本就等于用户感觉到的卡顿有多久。

同一段音频,三种前瞻设置下的延迟与准确率权衡时间轴 →块 1块 2块 3 当前前瞻未来当前块能看见:全部历史 + 自己 + 前瞻那一小段前瞻 0 ms · 纯因果延迟只剩块长本身同音字、连读、语尾变调全靠猜,错误率明显上升适合:语音指令、唤醒词前瞻 300~600 ms · 常用档准确率接近离线的九成多人耳对这个量级的延迟在对话场景里基本无感适合:实时字幕、语音助手前瞻 = 整段 · 退化成离线准确率上限,无幻觉之外的损失但用户要等到说完才看到字交互体验上等于没有流式很多「流式」其实是这一档伪装的最后那句要展开:把整段音频识别完再逐字吐给前端,用户看到的是文字在流,实际延迟仍是整段时长。SGLang-Omni 的文档把这种叫 pseudo-streaming,并明确写出 Qwen3-ASR 的流式路径不做切块 —— 真正的流式输入需要编码器本身支持增量,那是另一套模型结构。
评估一个「流式 ASR」时,第一个该问的问题不是准确率,而是首字延迟怎么测的。把整段跑完再逐字下发,和真正的增量编码,在 WER 上可能没有差别,在体感上差了一个数量级。

流式链路上还有两个常被低估的组件:

  • VAD(语音活动检测)决定了「从哪到哪算一句」。它切得太碎会把一句话拆成几段,上下文断裂导致识别变差;切得太粗则延迟上升。绝大多数流式系统的体感问题出在 VAD 参数上,而不是识别模型上。
  • 端点检测(endpointing)决定了「用户说完了没有」。这个判断直接触发下游的大模型开始回答,判早了打断用户,判晚了对话有空档。语音对话产品里,它比识别准确率更影响体感。

五、三个指标要一起看

供应商发来一句「我们的 WER 是 2.1%」,你该不该信?

信不了,因为这个数字单看没有意义 —— 换一套文本规整化规则(数字要不要转成汉字、标点算不算错、英文大小写怎么处理),同一个模型能测出相差一倍的 WER。

所以选型至少要同时问三个数,缺一个都能被糊弄过去:

指标测什么陷阱
WER / CER转写准不准依赖文本规整化规则。数字、标点、英文大小写怎么处理,能让同一个模型的 WER 相差一倍
RTFx吞吐(一秒能转写多少秒音频)是并发下的总吞吐,不是单请求速度。RTFx 500 可能来自「并发 32 每路 RTFx 15」
延迟 p95体感均值毫无意义。ASR 的延迟分布是长尾的,长音频和短音频混在一起时尤其明显

下面是一组真实测量(SGLang-Omni 在一张 H100 上跑 Qwen3-ASR-1.7B,SeedTTS 英文集 1088 条),把吞吐和延迟画在一起就能看出问题:

050.010000.20.40.6拐点18163264并发
悬停查看数值,点击图例可隐藏曲线
并发 32 是拐点:再往上加,吞吐不再涨(108 → 103,反而掉了),而延迟直接翻倍(0.295 → 0.610 秒)。同一时刻 GPU 利用率只有 33%~54%,卡是闲着的。

卡闲着,堆并发却没用 —— 这个组合说明瓶颈不在算力。它在准入排队、请求构建、以及自回归每一步在 CPU 侧的调度开销上。

一组真实的数字(SGLang-Omni 在 H100 上跑 Qwen3-ASR-1.7B,SeedTTS 中文集 2020 条):并发 1 时 14.3 req/s、RTFx 66.9、延迟均值 70 ms;并发 32 时 126.4 req/s、RTFx 591.6、延迟均值 252 ms;并发 64 时吞吐不再上升(129.2 req/s),延迟却翻倍到 489 ms,还开始丢请求。这个「拐点」的成因和排查过程,是07 篇的主要案例。

这里先给结论中最反直觉的一条:在那个拐点上,GPU 利用率只有 33%~54%。ASR 服务的瓶颈通常不在 GPU 算力,而在这几处:请求排队等着被放进运行批(这一步叫准入)、把请求组装成模型输入、以及自回归每一步在 CPU 侧的调度开销。拿到一个「ASR 服务不够快」的问题时,先看 GPU 利用率,再决定往哪个方向查。

下一篇04 - TTS 的两段式结构,反过来看文字怎么变成波形。