
按住快捷键,说两分钟,松手,字已经在光标处了。
早几年的语音输入不是这样。录完,整段音频丢给模型,等它一个词一个词吐出来。两分钟的录音等两三秒,一段会议纪要等十几秒。语音输入因此一直是个能用但不想用的功能,准确率再高也没人愿意等。
最近趋势榜上有个叫 HEX 的项目,用 Rust 写的,把这个体验改掉了。它的发布说明写得很直白:两分钟的听写,松手后 0.1 秒出字;同样的活,在 RTX 5070 Ti 上整段跑要 0.9 秒,在 MacBook Air M4 上要 2.8 秒。
0.1 秒和 2.8 秒之间差的不是模型,是延迟的结构。这篇文章想把这笔账拆开,因为拆开之后你会发现,语音听写真正的门槛,和大多数人以为的地方不一样。
一、整段和流式,是两种算法
整段转写的时间跟音频长度成正比。一段 120 秒的录音,模型要把 120 秒全编码一遍,再自回归地解码出几百个 token。这个时间没法压,你只能等。
流式转写换了账期。你还在说话的时候它就开始算,每过一小段就把这段音频送进模型,吐出一部分文字。等你松手,前面 118 秒的文字已经躺在内存里,只剩最后那一两秒需要现算。
于是延迟从"跟录音长度成正比"变成"跟最后一个分块的大小成正比"。你说三十秒还是说三分钟,松手后的等待是一样的。
这就是为什么 0.1 秒这个数字是结构性的,不是调优调出来的。同样的权重,同样一台笔记本,换了计算时机,等待时间差一个数量级。
二、那个"最后一块"到底有多大
HEX 用的英文模型是 NVIDIA 的 Parakeet Unified,0.6B 参数,24 层 FastConformer 编码器加 RNNT 解码器。它的设计挺巧:同一份权重既能做整段也能做流式,靠的是在编码器里塞不同的注意力掩码。
流式的做法是把音频切成三段看:左边一大段历史,中间当前分块,右边一小段往后看。延迟公式很干脆:
延迟 =(分块时长 + 右上下文时长)× 80ms
那个 80ms 是模型的帧率,8 倍下采样之后每一帧代表 80 毫秒音频。左上下文默认 5.6 秒,训练时定下来的,给模型足够历史判断断句和上下文。
于是延迟成了一个可以直接拧的旋钮。官方给的几档配置和对应的准确率,摊开是这样:
| 配置 | 理论延迟 | 平均 WER |
|---|---|---|
| 整段(不上流式) | 随长度增长 | 5.91 |
| 1.04 + 1.04 | 2.08 s | 6.14 |
| 0.56 + 0.56 | 1.12 s | 6.29 |
| 0.16 + 0.40 | 0.56 s | 6.52 |
| 0.08 + 0.24 | 0.32 s | 6.92 |
| 0.08 + 0.16 | 0.24 s | 7.35 |
| 0.08 + 0.08 | 0.16 s | 8.44 |
| 0.08 + 0.00 | 0.08 s | 15.63 |
这张表信息量很大,我读出三件事。
第一,从 2.08 秒压到 0.32 秒,准确率只从 6.14 掉到 6.92,不到一个点。这个区间的性价比高得离谱。
第二,继续往下压就开始崩,而且崩的位置很说明问题。0.32 秒往下那三档,分块一直卡在 0.08 秒没动,动的只有右上下文:从 0.24 缩到 0.16,WER 7.35;缩到 0.08,8.44;右边彻底不给,15.63。也就是说,前面那些延迟是靠"少往后看"省出来的,不是靠"少算历史"。右上下文大概到 2 帧(160 毫秒)收益就饱和,再砍就是拿准确率硬换。
第三,也是最容易被忽略的:HEX 选的是最慢的那档,2.08 秒。一个主打"说完就出字"的产品,偏偏选了延迟最大的配置。
看着矛盾,其实不是。流式的 2.08 秒是指"计算时用 2.08 秒的上下文",不是"你要等 2.08 秒"。它一边说一边把历史算掉,松手时只剩尾部一两秒。用最大的窗口拿最好的准确率,把等待藏进你说话的时间里,这才是这笔账真正的算法。
三、代价转嫁给了显卡
准确率是拿算力换的。这种缓冲式流式每处理一个分块,都要把左边 5.6 秒的上下文重新编码一遍。它不像另一种 cache-aware 的做法能把编码器的中间状态缓存下来,所以计算量是实打实多出来的。NVIDIA 自己在模型卡里承认,低延迟区间比 cache-aware 的 Nemotron 方案慢 100% 到 300%。
多出来的算力意味着这东西挑硬件。HEX 的做法很干脆:流式只在有 GPU 的机器上开,macOS 走 Metal,Windows 和 Linux 走 Vulkan,CPU 一律退回整段。所以 Windows 的 ARM64 版本从来不走流式,因为它就在 CPU 上跑。
同一份代码,同一套逻辑,插不插显卡是两种体验。这条对做本地工具的人是个提醒:能不能上某个优化,很多时候不取决于你想不想,取决于用户的机器。
顺带说个第三方实测的数字。有人把这份权重编成 CoreML,编码器处理一个 1.04 秒分块大约 12 毫秒。按这个速度,松手后现算的那一两秒音频,理论上几十毫秒就出结果,剩下的时间是解码、拼接、粘贴和界面反馈。0.1 秒这个总账,大致对得上。
四、空音频上的幻觉,藏得比想象中深
说完就出字有个隐蔽的坑:如果你按了快捷键但没说话,模型不该输出任何东西。可它偏偏会。
Whisper 这类模型训练语料里塞满了带字幕的网络音频,拿到一段沉默时,最可能的输出就是字幕套话。有人统计过自己生产环境一周的数据:3412 段转写里 187 段是幻觉,“Thank you.” 出现 71 次,“Thanks for watching!” 38 次,更离谱的是光秃秃一个 “you”,29 次。
常见的修法是调 no_speech_threshold,让模型自己判断"这段没说话"。这个修法基本没用。同一个人把阈值从默认降到 0.3,幻觉只从 187 段降到 97 段。原因是幻觉的 logprob 往往很高,模型非常自信地认为这里真有人在说"谢谢观看",那个判据抓不住自信的胡说。
管用的是在模型前面加一道闸:先用 VAD 把非语音切掉,让模型根本收不到静音。加了闸之后 187 降到 9,再补一个幻语黑名单降到 2。
HEX 在这件事上做了两层,顺序讲究。第一层是个保守的能量阈值,定得低,宁可放过也不误杀,因为误杀真语音的代价比放过一段噪音大得多。过了第一层,再交给 Silero VAD 判到底有没有人说话。两级失败的方向不一样:单靠能量,敲键盘、房间噪音、开始提示音都能把电平抬上去;单靠 VAD,短促的单字又容易漏。
还有一处很人性的设计:松手之后它还会多录 200 毫秒。因为人的最后一个字经常被切掉,尤其是指令性的短句。这 200 毫秒是明码标价的延迟成本,买的是"最后一个字不丢"。
五、转写只占一半,另一半是把字塞进光标
模型出字了,难点才刚开始。文字得落在你当前光标的位置,不管你在用 VS Code、浏览器、Slack 还是某个 Electron 套壳的聊天窗口。
这一步在每个系统上都是一套单独的活。macOS 走 Accessibility 接口模拟输入。Windows 要等你把 Shift、Alt、Win 全松开再发粘贴,不然那几个键会被当成组合键的前缀;按住 Ctrl 反而没事,它跟粘贴不冲突。Linux 的 X11 可以直接抓,Wayland 就麻烦:得靠 wtype 的虚拟键盘协议,或者桌面提供的 RemoteDesktop portal,而 GNOME 和 KDE 不在当前这套协议的完整覆盖范围内。
中间还有个细节:粘贴要走剪贴板,用户原来复制的东西怎么办。HEX 的做法是粘完把旧剪贴板恢复回去,只在没有更新的剪贴板变更时恢复。这种细节不写进文档基本没人会注意到,但少做一次,用户就会觉得这软件把我剪贴板搞坏了。
所以我一直觉得,语音输入这类工具的产品差异,大部分不在识别率上。识别率是模型给的,大家用同一批开源权重,差不了太多。真正拉开差距的是最后一步:同一个词能不能准确落到该落的地方,失败时有没有声音提示,取消时会不会留下半截字。
六、“本地"这两个字要说清楚
HEX 主打本地优先,但本地转写不等于全程离线,这话得说明白。模型第一次用要从 Hugging Face 下载;自动更新要走自家发布服务;可选的文本改写要发到你配置的云端模型;语音命令是另一个模型,从另一个域名拉。真正的边界是:说话这一段音频不出机器,但周边的东西要联网。
它文档里还有几个数字,我认为值得所有做本地工具的人抄走:默认不保存听写音频;历史只留成功粘贴的文本,默认 7 天;每条记录有 1 万条和 16MiB 的双重上限,超了先淘汰最旧的;诊断日志里可能有转写文本和前台应用名,但不会上传。
这些数字看着琐碎,其实是把"隐私"从形容词变成了可以审计的东西。说"我们重视隐私"没有意义,说"音频不落盘、文本七天、上限 16MiB"才有意义。
七、一个罕见的诚实
最后说个跟技术无关但我很在意的细节。HEX 的 Windows 文档里写着:这个 app 从来没有在 Windows 上跑过。
一个冲上趋势榜第四的项目,公开承认 Windows 版本是交叉编译出来的,只过了类型检查和 clippy,还没有人在真机上点过一次。它甚至把"在 Windows 10 和 ARM64 真机上各跑一遍"列成了发布前的待办。
这比写一句"支持 Windows"强太多。用户看到"支持"会以为有保证,看到"没跑过"就知道自己在当小白鼠,可以自己决定要不要试。工程文档的价值不在漂亮,在于让读的人知道哪里能信、哪里得自己验。
怎么判断一个听写工具值不值得用
别看模型榜单,看三个数。
松手到出字多少毫秒。这是体感的全部,超过 300 毫秒人就会觉得卡,超过 1 秒就开始怀疑是不是没录上。
流式在什么硬件上生效。宣传页说 0.1 秒,可能只在独显上成立。问清楚你的机器算不算数。
流式失败之后会不会回退。这条最关键,也最少人问。流式是有截止时间的,一旦超时或者返回空,必须有兜底路径把整段重跑一遍。HEX 会把每次听写记成 source=stream 或 source=fallback,这个日志字段看着不起眼,但它让你知道今天到底走了哪条路。没有回退机制的产品不会报错,只会让你觉得今天有点卡,然后你就再也不用了。
语音输入这件事,从"能识别"到"愿意用”,中间隔着的往往不是准确率,是那 300 毫秒。
参考来源
- anomalyco/hex :本地听写工具的仓库与平台文档
- HEX 发布说明 2.3.0 :流式听写的延迟数据与各平台差异
- nvidia/parakeet-unified-en-0.6b :统一的整段与流式模型,含延迟与 WER 对照表
- Whisper Hallucinates on Silence :一周 3412 段音频中 187 段幻觉的实测与修法