两个长得很像的实时转写开关,别调错

2026-08-09

speech-to-speech 的参数表里有一对特别阴的东西:--enable_live_transcription--enable_realtime_transcription。名字翻成中文都是「实时转写」,但它们来自两个不同的参数文件,默认值一个是 True 一个是 False,管的事情也不是一回事。更糟的是,它们各自还带了一个「间隔 0.5」的伙伴参数和一个「最小静音毫秒」的伙伴参数,四个数字两两撞脸。

这篇按排查的路子走一遍:现象、怎么确认是这个问题、参数语义给出的处置、处置后怎么验证、以及什么情况说明根本不是这个原因。

一、现象长什么样

典型的三种描述:

  • 「我压根没配实时转写,日志里却一直在刷用户说话的中间结果。」
  • 「我明明把实时转写打开了,客户端就是收不到局部转写,只有一整段最终结果。」
  • 「我想让中间结果出得快一点,把那个 0.5 改小了,一点变化都没有。」

这三句话的共同点是:说话人心里只有「实时转写」这一个概念,而仓库里有两个开关、两条路径。改错一个,看起来就是「参数不生效」。

二、怎么确认是不是这一对参数在打架

判定动作 1:先确认参数属于哪个文件

这是最快的一刀。两个开关在源码里的归属是:

参数所属文件默认值help 原文要点
--enable_live_transcriptionmodule_arguments.pyTrue在用户说话时启用实时转写显示,help 里注明「works with parakeet-tdt」
--enable_realtime_transcriptionvad_arguments.pyFalse启用说话过程中的渐进式音频释放,以支持实时转写

一个在模块级(和 --stt--tts--llm_backend--num_pipelines 挤在同一个文件里),一个在 VAD 侧(和 --thresh--min_silence_ms--speech_pad_ms 挤在一起)。

模块级那个默认就是开的,所以「我没配它却在刷中间结果」不是玄学,是默认值。VAD 侧那个默认是关的,所以「我以为我开了」多半是把模块级那个又写了一遍。

判定动作 2:跑一次 -h,按分组看,别按名字看

speech-to-speech serve -h

看输出时不要盯着关键字搜 transcription,要看这一项落在哪一组参数里。这两个开关名字的区分度太低,肉眼扫过去很容易读成同一个;分组和默认值才是可靠的区分特征。

判定动作 3:把伙伴参数一起摆出来

真正让人改错的不是开关本身,是它们各自的两个伙伴:

意图模块级(module_arguments.pyVAD 侧(vad_arguments.py
开关--enable_live_transcription(默认 True--enable_realtime_transcription(默认 False
更新/释放间隔--live_transcription_update_interval(默认 0.5,单位秒)--realtime_processing_pause(默认 0.5,单位秒)
最小静音--live_transcription_min_silence_ms(默认 500--min_silence_ms(默认 64

两个「0.5 秒」正对着,这就是「我把 0.5 改小了没反应」的来源——改的那个跟你想要的那条路径不在一起。

而两个「最小静音」差了将近一个数量级:--live_transcription_min_silence_ms 的 help 说的是「启用实时转写时,结束语音之前的最小静音时长」,默认 500 ms;--min_silence_ms 是 VAD 用来切分语音段的最小静音间隔,默认只有 64 ms。你要是把它们当成同一个东西,一改就会连带影响整条回合切分。

顺带说一句单位陷阱:这一堆参数里只有带 _ms 后缀的是毫秒,--live_transcription_update_interval--realtime_processing_pause 都是

三、按参数语义该怎么处置

分三种意图对号入座。

意图 A:我要的是用户说话过程中就有局部转写往外发。 需要的是 VAD 侧那个——它的 help 讲得很直白,作用是「渐进式音频释放」,也就是不等整段话说完就先把音频块放出去。它默认是关的,得显式加:

speech-to-speech serve \
    --enable_realtime_transcription \
    --realtime_processing_pause 0.5

(以上为按官方参数语义组合的示例,未逐项实测,以官方文档与 -h 的实际输出为准。--realtime_processing_pause 0.5 写的就是默认值,放在这里是为了让你知道该改哪一个。)

意图 B:我不想要说话过程中的实时转写显示。 那你要动的是模块级的 --enable_live_transcription,因为它默认是 True。这里有个必须如实说明的地方:这个仓库里我们有明确依据的布尔取反写法只有 Smart Turn 那一个——help 原文写的是「pass --no_smart_turn to disable it」。其它布尔参数的取反写法官方 help 里没有逐个写出来,请以你本机 -h 的实际输出为准,别照着 --no_smart_turn 的样子自己拼一个出来。

意图 C:我要调「多久算说完一句」。 先想清楚你要调的是哪一层。--live_transcription_min_silence_ms(500)按 help 的说法是实时转写启用时结束语音的门槛;--min_silence_ms(64)是 VAD 切分语音段的基础参数。后者默认值本来就很低,切得碎是预期内的;仓库为此另给了一个补救开关 --short_segment_merge_ms(默认 0,即关闭),它的 help 明说「在 min_silence_ms 取值很低时有用」——大于 0 时,相邻的、每段都短于 --min_speech_ms(默认 384)的 VAD 段会被暂存并缝合这么多毫秒,且活跃语音短于 100 ms 的碎片永远不会被暂存。

四、处置之后怎么验证

第一,回读 -h 里的默认值。 改完之后重新看一次分组输出,确认你写在命令行上的那个名字,和你想要的那一组落在同一个文件里。这一步听起来废话,但这对参数的翻车率高就高在肉眼读名字。

第二,看客户端收到的事件名。 用户侧的局部转写走的是 conversation.item.input_audio_transcription.delta,最终结果是 conversation.item.input_audio_transcription.completed。这两个事件名是判断「实时转写这条路径有没有通」的直接依据:delta 在流、就是通了;只有 completed、没有 delta,说明局部转写没往外发。

第三,把日志级别调上去。

speech-to-speech serve --log_level debug

--log_level 默认是 info,help 原文举的例子就是 --log_level debug

第四,别用「感觉快了没有」来验收。 这一步是这篇文章里最想强调的一条:这些参数默认值是流水线自己引入的、可配置的等待,它们不包含 STT 推理、LLM 首 token、TTS 首包这三段真正吃算力的时间。你把几个毫秒数加起来得到的任何「端到端延迟」都是假的。验收就看事件有没有按预期的名字出现,不要看体感。

五、什么情况说明不是这两个参数的问题

这一节比前面四节都重要——改错了参数固然常见,但下面这五种情况改到天亮也没用。

情况一:你缺的是助手侧的转写,不是用户侧的。 助手输出的转写走的是完全另一组事件:response.output_audio_transcript.delta 是当前音频输出项的助手转写增量后缀,response.output_audio_transcript.done 是输出项关闭时发一次的完整转写。仓库 README 为这件事单开了一节讲兼容性变更:以前消费每个块级 done 事件的客户端,必须把实时渲染改到 delta 上、把 done 只当作定稿。如果你的客户端只监听 done,那问题出在客户端的事件消费方式上,跟这两个开关没有半点关系。

情况二:你换了 STT 后端。 --enable_live_transcription 的 help 里注明的是「works with parakeet-tdt」。换成别的 STT 之后官方 help 没有给出说法,我们也不替它下结论——但这至少意味着,当你不在默认 STT 上时,「开关没生效」的可能原因就不止参数写错这一个了。

情况三:你说的是中文,而且什么转写都没有。 这多半根本不是开关问题,是选型问题。默认 STT 是 parakeet-tdtnvidia/parakeet-tdt-0.6b-v3),README 的多语言表里写明它覆盖的是 25 种欧洲语言;--language 默认值是 en。要做中文语音代理必须换 STT,README 给出的中文示例是这样的:

speech-to-speech serve \
    --stt whisper-mlx \
    --stt_model_name large-v3 \
    --language zh \
    --llm_backend mlx-lm \
    --model_name mlx-community/Qwen3-4B-Instruct-2507-bf16

在这种情况下,你先要解决的是「模型认不认得中文」,转写开关是后面的事。

情况四:第二个客户端根本连不上。 --num_pipelines 默认是 1,help 原文说得很清楚:一个 uvicorn 服务监听 --port(默认 8765),把每个进来的客户端路由到下一个空闲流水线,最大并发 WebSocket 会话数等于 num_pipelines,再多的连接会被拒绝。连接被拒和转写不出来是两码事,别在参数表里找错方向。

情况五:转写在来,只是你觉得「等得久」。 那是回合判定那一层的事,跟这两个开关无关。默认开着的 Smart Turn(--smart_turn 默认 True)会在 Silero 敲定轮次之后决定助手输出保持推测状态多久:判为完整的轮次用 --speculative_reopen_ms(默认 800 ms)的提交窗;判为不完整的轮次先等 --smart_turn_incomplete_delay_ms(默认 600 ms)再启动 STT 与 LLM 工作,其输出继续被 --smart_turn_max_wait_ms(默认 2000 ms)门控;另有 --unanswered_reopen_ms(默认 7000 ms)作为「还没有任何助手输出的轮次」的兜底上限,它低于 --speculative_reopen_ms 时无效,启用 Smart Turn 时被钳制到 --smart_turn_max_wait_ms。这些数字买的是误判成本,不是延迟——再说一次,不要把它们相加。

延伸阅读


把这一整套压成一句能记住的话:名字里带 live 的在模块级、默认开着;名字里带 realtime 的在 VAD 侧、默认关着。 改参数之前先确认自己站在哪一层,比记住哪个数字是多少有用得多。


本文依据 speech-to-speech 官方仓库(github.com/huggingface/speech-to-speech)的 README、 src/speech_to_speech/arguments_classes/ 下的参数定义与 Realtime Engine 架构文档整理,核对日 2026-08-09。 本文内容为仓库源码与文档口径,我们没有安装、部署或调用过该服务,文中毫秒值均为参数默认值而非实测延迟。 参数与默认值随版本变动,请以 speech-to-speech serve -h 的实际输出为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。