语音对话延迟预算计算器
从「用户说完」到「听到第一声回复」,拆成可加总的几段,先定位卡在哪儿。
把一次语音对话的「用户说完 → 听到第一声回复」拆成可加总的几段,帮你定位卡在哪一段。 下面的输入分成两类,性质完全不同,请分开看。
A 类|流水线配置引入的等待
默认值取自仓库参数默认值,不是实测延迟这几个数字是 speech-to-speech 流水线自己引入的、可配置的等待项。改配置就会变, 它们描述的是「等多久」,不是「跑多久」。
B 类|算力引入的耗时
没有预设值,必须你自己实测填入这四段取决于你的模型与硬件。本站没有测过任何一台机器,所以这里一个数字都不给你—— 填你自己跑出来的毫秒数。
分段耗时
| 分段 | 类别 | 耗时 | 占比 |
|---|---|---|---|
VAD 静音判定 --min_silence_ms | A 配置等待 | 64 ms | 7.4% |
推测重开窗口 --speculative_reopen_ms | A 配置等待 | 800 ms | 92.6% |
STT 转写 你自己实测 | B 算力 | 未填 | 0.0% |
LLM 首 token 你自己实测 | B 算力 | 未填 | 0.0% |
TTS 首包 你自己实测 | B 算力 | 未填 | 0.0% |
网络往返 你自己实测 | B 算力 | 未填 | 0.0% |
最大的一段:推测重开窗口(800 ms,占 92.6%)。它是配置等待项,想缩短就改这个参数——代价是误判成本上升,参数本身是拿等待换判断准确度的。
B 类还有 4 段没填,当前合计只是个残缺的下界, 别拿它当端到端延迟看。
本站没有任何实测数据:A 类是可配置的等待项(默认值取自仓库参数默认值,不是实测延迟), B 类必须你自己测。这个页面只做加法,不预测你的延迟。
为什么要把延迟拆开算
语音对话卡顿,最常见的处理方式是「换个更快的模型试试」。但这一步经常白做—— 因为你不知道那一秒钟到底花在哪儿了。它可能根本不在推理上,而在流水线主动等你的那几百毫秒里。
一次语音回合的首响应延迟,大致由这样一串组成:你停止说话 → VAD 判定这是一段静音 → Smart Turn 校验「这轮真的说完了吗」并产生对应的等待 → STT 把音频转成文本 → LLM 吐出第一个 token → TTS 合成出第一块音频 → 经网络传到你耳朵里。
这一串里,前半段和后半段的性质完全不同,而这正是这个工具把输入分成两类的原因。
两类数字,别混着看
A 类是流水线配置引入的等待。它们是 speech-to-speech 仓库里的参数, 页面里给出的默认值取自该仓库的参数默认值——是配置项没被改动时的取值,不是实测延迟。 这类数字的特点是:你改配置它就变,而且变化是确定的,不依赖任何硬件。
B 类是算力引入的耗时。STT 转写、LLM 首 token、TTS 首包、网络往返。 这四段取决于你选的模型、跑在什么卡上、服务部署在哪、并发多少。 本站没有跑过任何一台机器,所以这四个输入框里一个预设数字都没有, 必须你自己测了填进去。这不是偷懒,是给不出来——凡是给你「参考区间」的表格, 你都该先问一句它测的是哪块卡、哪个模型、什么并发。
三条路径分别怎么等
Smart Turn 是一个外部模型(pipecat-ai/smart-turn-v3,CPU ONNX),
用当前轮次的内容与韵律来校验 Silero 给出的「说完了」判断。它默认是开的,
要关得显式传 --no_smart_turn。判定结果不同,等待项的组成就不同:
- 判定完整:立刻开始处理,提交输出前使用推测重开窗口。
等待项 =
min_silence_ms+speculative_reopen_ms。 - 判定不完整:先晾一会儿再启动 STT/LLM,让可能恢复的语音有机会
在昂贵计算开始之前把这次修订作废。等待项 =
min_silence_ms+smart_turn_incomplete_delay_ms,而输出继续被smart_turn_max_wait_ms门控。 - 关闭 Smart Turn:没有不完整这条路,回到
min_silence_ms+speculative_reopen_ms。
所以这几个毫秒数字买的不是延迟,而是误判成本—— Silero 一说「完了」就开工能省时间,但用户只是喘口气的话这活就白干了。 Smart Turn 给「白干」上了一个成本可控的闸。参数细节可以看 VAD 参数全解和 Smart Turn 的判定机制。
怎么用
第一步,选路径。你想分析哪种情况?句尾干净利落的短问句多半走「判定完整」, 说话带思考停顿的长句更容易走「判定不完整」。两条都算一遍,差值就是不完整判定给你带来的额外等待。
第二步,A 类按需改。如果你改过配置,把你实际启动命令里的值填进去;
没改过就留着默认。注意 min_silence_ms 默认只有 64——
这个值很小,切得很碎,别把它记成 640。
第三步,B 类老老实实测。最省事的做法是在流水线各环节前后打时间戳, 跑十几轮取个中位数,别用第一次冷启动那一轮(模型加载会把数字撑得很难看)。 网络往返在本机跑就填 0。
第四步,看「最大的一段」。如果最胖的是 A 类,说明你卡在配置的等待上, 改参数就能动——但要清楚代价是误判成本上升。如果最胖的是 B 类,改流水线参数没用, 得从模型、量化、硬件或部署位置上想办法。
为什么门控上限不加进合计
smart_turn_max_wait_ms(默认 2000)经常被误当成「不完整轮次要等 2 秒」。
它不是一段串行耗时,是一个上限:判不完整时那 600 ms 的延迟跑在这个宽限期之内,
输出继续被它门控;期间只要语音恢复,现有轮次就被重开为一个更新的修订版,
累积的音频重新发出,上一版的工作在到达用户之前就被丢弃。
把上限加进总和,等于把「最坏情况的天花板」和「这一次实际串行的等待」混成一个数。 所以页面把它单独列出来。三个重开窗口之间还有钳制关系,容易讲错, 单独写在三个重开窗口的层级关系那篇里。
它算不了什么
它不预测你的延迟。它只把你填的数加起来,再告诉你哪一段最胖、各占多少。 真实链路里还有一堆这里没建模的东西:音频采集与缓冲、进程间排队、TTS 播放启动、 抢话与重开导致的返工。而且 A 类是「等待」、B 类是「计算」, 在真实实现里未必严格串行——推测式执行本来就是让计算提前于确认发生的。
它也不会告诉你「你的显卡够不够」「这个延迟算好还是坏」。 本站没有任何实测数据,任何关于「某某模型首 token 大约多少毫秒」的说法都不会出现在这个页面上。 想了解整条链路的拆法,可以接着看 语音延迟预算怎么拆, 或者从speech-to-speech 专题进去按顺序读。
常见问题
为什么 A 类给了默认值,B 类一个数都不给?
因为两类数字的性质完全不同。A 类那几个毫秒(min_silence_ms=64、speculative_reopen_ms=800、smart_turn_incomplete_delay_ms=600、smart_turn_max_wait_ms=2000)是 speech-to-speech 仓库里参数的默认取值,你不改配置它们就是这个数,属于可以查证的事实——但它们描述的是「流水线主动等多久」,不是「跑一次要多久」。B 类那四段是 STT、LLM、TTS 的推理和网络,取决于你用哪个模型、跑在什么硬件上、部署在哪。本站没有跑过任何一台机器,给不出数;给了就是编。所以 B 类的输入框里一个数字都没有,占位符只写「请填你实测的毫秒数」。
三条路径为什么等待项组成不一样?
Smart Turn 的作用是校验 Silero 给出的「说完了」判断,判定结果不同,后续流程就不同。判为完整:立刻开始处理,提交输出前使用 speculative_reopen_ms 的重开窗口,所以等待项是 min_silence_ms + speculative_reopen_ms。判为不完整:先等 smart_turn_incomplete_delay_ms 再启动 STT/LLM,让恢复的语音有机会在昂贵计算开始前作废这次修订,所以等待项换成 min_silence_ms + smart_turn_incomplete_delay_ms。关掉 Smart Turn(--no_smart_turn,注意它默认是开的)就没有不完整这条路,回到 min_silence_ms + speculative_reopen_ms。
smart_turn_max_wait_ms 是 2000,为什么不加进合计?
因为它不是一段串行耗时,是一个上限。判不完整时,那 600 ms 的延迟是跑在这个 2 秒宽限期之内的,输出继续被它门控;期间语音恢复,轮次就被重开为一个更新的修订版,上一版的工作在到达你耳朵之前就被丢掉。把上限加进总和,等于把「最坏情况的天花板」和「这一次实际串行的等待」混成一个数,算出来的东西没有意义。所以页面把它单独列出来,只当上限看。
算出来合计一千多毫秒,是不是就代表我的语音助手延迟这么多?
不是。这个页面做的是加法,加的是你自己填的数,准确度完全取决于你测得准不准。而且真实链路里还有一堆这里没建模的东西:音频采集与缓冲、进程间排队、TTS 播放启动、抢话与重开导致的返工。更重要的是,A 类那几段是「等待」,B 类那几段是「计算」,两者在真实实现里未必严格串行,推测式执行本来就是让计算提前于确认发生的。所以把它当成定位工具用——看哪一段最胖,就往哪儿下手——而不是当成延迟预测器。
想把语音 Agent 真正跑起来?
从装环境到调回合参数,奇连 AI 的 speech-to-speech 专题按顺序排好了。