作者在数据不能离线的合规要求下,选型离线注意力编码器-解码器模型,配合滚动窗口和局部一致性策略实现实时听写。
当这个需求最初落到我桌上时,听起来很简单:构建一个实时的、基于光标的听写功能。用户对着浏览器说话,文字会实时出现在文本光标处,这样他们就可以起草长文档了。
但有一个问题。这个领域使用了大量专业词汇,而且音频受到严格监管。向云端 API 发送数据是不可能的——音频永远不能离开我们的服务器。
这是我第一次深入语音转文字(STT)工程领域的经历。以下是我探索了什么、我遇到了哪些残酷的障碍,以及我最终交付的架构。
核心抉择:伪造流式还是原生流式
我的第一个架构十字路口是选择模型家族。我最初看了离线注意力编码器-解码器模型。它们以高准确率著称。然而,它们是用来转录完整音频文件的。
要将离线模型用于"实时"听写,你必须伪造它。你维护一个音频的滑动窗口(比如最近几秒),每 0.5 秒重新转录整个窗口。为了决定实际保留哪些文本,你要等到两次连续的转录对一个词达成一致(局部一致)然后再提交它。
我很快意识到这是一个陷阱。它不断重新转录重叠的音频,消耗大量 CPU 周期。更糟糕的是,当输入绝对静音时,这些离线模型会产生大量幻觉,发明出股票短语或结束语。因为幻觉在多次传递中是稳定的,系统会永久地提交一连串重复的幽灵文字。
转向:我放弃了离线方法,转向流式神经转录器(RNN-T 家族)。
转录器的构建方式不同。它们在音频到达时即时输出 token,并且有内置的"端点检测"概念(检测停顿以标记话语结束)。它们原生支持实时、数学上稳定,而且最重要的是,它们不会凭空产生幻觉文字。
关键收获:如果你要构建实时听写,强行使用离线模型是在对抗用例本身。流式转录器才是正确的工具。
搞定 UX:两态契约
为了让实时光标感觉神奇而不是突兀,我需要一个坚如磐石的 UI 契约。对于起草长文档的用户来说,一个跳动的、闪烁的文本光标非常令人沮丧。
我实现了一个两态输出契约,每个 WebSocket 更新携带两个字段:{committed, buffer}。
Committed:客户端永久追加的稳定文本(以正常字重渲染)。
Buffer:模型当前的临时猜测(以灰色或斜体渲染,每次更新都会替换自己)。因为流式模型自然处理端点检测,当用户暂停时它会发出最终文本。客户端将 buffer 晋升为 committed,流重新复位。稳定文本永远不会向后跳跃,而临时尾部会可见地稳定下来。
障碍 1:沉默的 WebSocket 杀手

让浏览器捕获音频很简单。浏览器将麦克风音频录制为 Opus 压缩块,每约 250 毫秒通过 WebSocket 流式传输一次。然后服务器将其解码为模型所需的原始 16 kHz 单声道 PCM。
然后遇到了项目中最难的工程 bug。
听写在开头一两个句子完美运行,然后 WebSocket 会随机断开连接。没有错误,只是连接丢失。
我追踪到一个并发和背压故障。最初,一个任务同时读取解码后的音频并内联运行 STT 模型。当模型忙于计算时,没有任何东西在排空音频解码器的输出管道。管道满了,解码器停止拉取输入,上游套接字阻塞,WebSocket 接收循环停滞。keepalive ping/pong 停止,连接死亡。
修复:我将管道解耦为两个独立的任务。
一个 Reader:持续将解码器排空到内存缓冲区,这样管道永远不会阻塞。
一个 Processor:消费那个缓冲区并以自己的节奏运行模型。
解耦后,背压永远不会到达套接字。
障碍 2:格式化和领域术语
文档需要大写字母和句号。许多流式模型输出归一化文本(小写、无标点)。
我有两个选择:在 committed 文本上运行辅助标点恢复模型,或者找一个原生处理大小写的模型。我选择了后者。通过检查模型的 token 词表中的混合大小写片段和标点 token,我完全绕过了对辅助格式化层的需求。
处理专业词汇更棘手。通用模型在处理冷门术语时会遇到困难。虽然上下文偏置(词提升)很棒,但我的流式模型的轻量级运行时只支持贪婪解码,意味着无法使用声学偏置。
我用后 ASR 校正映射(Post-ASR Correction Map)务实地解决了这个问题——一个简单的 {误听 -> 正确} 术语字典,应用到 committed 文本上。它不如声学偏置优雅,但能根据实际用户数据可靠地捕获可重复的错误。
部署现实:CPU 优于 GPU
你可能认为实时 AI 模型需要强大的 GPU 算力。实际上,流式转录器非常高效。
实时因子(RTF)是处理时间除以音频时长。对于一个约 0.6 亿参数的流式模型,我在标准笔记本电脑 CPU 上测量的 RTF 约为 0.09。这意味着有 11 倍的实时余量。
这导致了一个反直觉的部署策略:
远离 GPU:将这个 STT 模型与大型 LLM 放在单个 GPU 上是个坏主意(我已经在服务器上部署了一个 LLM,但 CPU 没有被利用)。LLM 突发生成会阻塞对延迟敏感的音频流,导致可听见的卡顿。在 CPU 上运行:ASR 在闲置的 CPU 核心上运行得很舒适。我将它包装在一个带有硬性 CPU 和内存限制的容器中,这样它就不会超出自己的车道或在服务器上触发内存溢出(OOM)崩溃。
从零开始构建这个让我认识到,应用 AI 最困难的部分往往不是神经网络本身——而是管道工程、背压处理和 UX 契约。
选择自托管流式模型意味着放弃了云 API 的自动更新和便捷的术语提升能力。但对于隐私监管环境,这是唯一诚实的选择。我们实现了真正的实时、零成本、私密听写——而且光标感觉恰到好处。