上一篇拆全双工语音对话综述时,有个场景反复出现:你说「我想要……那个……」,停顿一秒半,助手立刻抢答——声学上的静默被当成了「说完了」。综述把这类失败归到 L0 模块级的一格:缺一个语义级的轮次结束检测器(EoT,End-of-Turn)。
这一篇拆一个正好补这格的开源项目:Smart Turn。它来自 Pipecat 生态(Daily 公司主导的开源语音 agent 框架),自我定位很清楚——一个「你说完了吗」的二分类器:听一段音频,输出一个概率,说完还是没说完。模型只有 8 MB,CPU 上十几毫秒出结果,权重、训练数据、训练代码全部开源(BSD 2-clause)。
麻雀虽小,五脏俱全:它把「借大模型的编码器、砍到只剩需要的部分、量化到 CPU 白菜价」这条小模型路线走了个完整来回,数据管线里全是值得抄的工程细节。下面按「为什么难 → 模型长什么样 → 三代怎么演进 → 数据怎么造 → 怎么装进 agent → 边界在哪」的顺序拆。
一、判停为什么难:VAD 的两难
语音 agent 的回合切换,业内叫 turn detection(轮次检测):决定「此刻该不该接话」。传统做法是 VAD(Voice Activity Detection,语音活动检测):把音频分成「有人声/没人声」,静音持续超过阈值就判定用户说完。
问题在于阈值没有好的取值:
- 阈值设短(比如 500 毫秒):用户想事、喘气、说「嗯……」都会触发抢答。上一篇的说法:固定阈值 VAD 在犹豫场景必然抢跑。
- 阈值设长(比如 2 秒):不抢话了,但每一次正常交接都要干等 2 秒,对话像隔着卫星链路。
根源是 VAD 只测能量,不懂内容。人类判断对方说完没有,靠的是语法完整性、语调走向、语速节奏——这些信息全在音频里,VAD 全扔了。Smart Turn 项目 README 里给了一组最小对比:
- “I can’t seem to, um …” —— 没说完,等着;
- “I can’t seem to, um, find the return label.” —— 说完了,接话。
静音长度可以完全一样,答案却相反。这就是「语义判停」(项目自称 semantic VAD)要解决的事。
值得强调的一个设计决定:Smart Turn 直接吃 PCM 音频,不走「先转写成文字再判断」的路。官方博客给的理由有两条:其一,语调和语速本身就是关键线索——「说完了」的降调和「没说完」的悬置感,转写后荡然无存;其二,ASR 模型往往会把 “um”「嗯」这类填充词直接吞掉,而这恰恰是判停最需要的词。
二、模型长什么样:借 Whisper 的耳朵,装自己的头
Smart Turn v3 的架构可以一句话说完:Whisper Tiny 的编码器 + 注意力池化 + 一个小分类头。总共约 800 万参数,int8 量化后 8 MB。
按训练代码 train.py 逐段过一遍:
输入端:永远只看最后 8 秒。 16 kHz 单声道 PCM,超过 8 秒截头留尾;不足 8 秒在前面补零,让真实音频始终贴着窗口右端——「最近的瞬间」永远在固定位置,模型不用学「说话声可能出现在任何地方」。8 秒音频经 Whisper 特征提取器变成 80 个 mel 频带 × 800 帧的 log-mel 频谱图。
编码器:Whisper Tiny,只要一半。 Whisper 是 OpenAI 的开源语音识别模型家族,Tiny 是其中最小的一档(3900 万参数)。语音识别是编码器-解码器结构:编码器把音频压成向量序列,解码器把向量翻译成文字。判停不需要知道每个字是什么,只需要「这段话听起来完没完」——所以 Smart Turn 把解码器整个扔掉,只留编码器,并把上下文长度从 Whisper 默认的 30 秒改成 8 秒(max_source_positions = 400)。3900 万参数就这样砍到约 800 万。
池化:让模型自己决定听哪里。 编码器输出 400 个时间步、每步 384 维。判停线索分布不均匀——句尾的语调、最后一个词是不是连接词,比 5 秒前的内容重要得多。所以这里不用平均池化,而是一个小注意力:每个时间步过两层线性网络打一个分,softmax 归一化成权重,按权重加权求和成单个 384 维向量。哪些帧重要,模型训练中自己学。
分类头:三层 MLP。 384 → 256(LayerNorm + GELU + Dropout)→ 64(GELU)→ 1,sigmoid 输出「说完了」的概率,阈值 0.5。
训练配方同样朴素:BCE 损失(按 batch 内正负比例动态加权)、学习率 5e-5、4 个 epoch、batch 384、cosine 调度。没有花活——这个任务的难点根本不在优化器上,在数据上(第四节)。
三、三代演进:一部「越小越准」的反直觉史
Smart Turn 的版本史有个漂亮的反直觉弧线:模型从 2.3 GB 一路缩到 8 MB,缩小近 300 倍,准确率反而一路上涨。
v1:大力没出奇迹。 第一版选了 wav2vec2-BERT 做底座——它在 450 万小时多语言数据上预训练过,团队希望这份底子能让判停自动泛化到多语言。整个模型 2.3 GB,只支持英语。
v2(2025 年 7 月):小的反而准。 训练 v2 时团队发现,wav2vec2-BERT 在未见数据上的表现反而不如更小的普通 wav2vec2,怀疑是大模型过拟合。他们还试了 LSTM、在 wav2vec2 后面加 Transformer 分类层等方案,最后胜出的是最朴素的「wav2vec2 + 线性分类头」:360 MB(缩小 6 倍),L40S 上推理 12.5 毫秒(快 3 倍),语言从 1 种扩到 14 种。在人工录制并清洗过的英语测试集上约 99% 准确率。
v3(2025 年 9 月):换底座,上 CPU。 再换 Whisper Tiny 编码器,配上 v2 的分类头,8 MB int8 量化——比 v2 小近 50 倍,语言扩到 23 种,测试集准确率还高于 v2。这一版的真正意义是把推理搬上了 CPU:现代 CPU 上约 12 毫秒,廉价 AWS 实例上约 60 毫秒(c8g.medium 上从 v2 的 6272 毫秒到 59.8 毫秒,快了 100 倍)——判停从「要配 GPU 的组件」变成了「跑在 agent 同一台机器上白送的组件」。官方给的 CPU 推理调优也实在:ONNX 单线程顺序执行,另设 OMP_NUM_THREADS=1,避免线程争抢反而更快更稳。
之后是两次小步快跑(都是换数据不换架构,新权重直接替换旧文件):
- v3.1(2025 年 12 月):引入三家数据公司(Liva AI、Midcentury、MundoAI)贡献的真人录音(英语 + 西班牙语)。在新测试集上,英语准确率从 88.3% 提到 94.7%——一次数据更新 +6.4 个百分点,比任何架构改动都值钱(下一节展开)。同时开始提供两个变体:8 MB int8 版给 CPU,32 MB fp32 版给 GPU(更快,准确率再高约 1 个百分点;注意在 CPU 上反而是 int8 版快得多)。
- v3.2(2026 年 1 月):专治两个实战痛点。短句(“yes”「好的」这类单词回答)误判率降 40%——靠新增短句数据集,外加修掉一个社区报告的训练时 padding bug;再往训练和测试集里混入真实的咖啡馆/办公室背景噪声,提升嘈杂环境的稳健性。
量化方案也值得记一笔:静态 int8 量化,用 1024 条训练样本做 Entropy 校准,只量化 Conv/MatMul/Gemm 三类算子,权重 int8、激活 uint8。8 MB 的文件里,这套组合拳保住了绝大部分精度。
四、数据的故事:合成数据的天花板,真人数据的补课
判停模型的数据有个先天难题:哪里去找几十万条「话说一半」的录音?正常人不会对着麦克风专门录「我想要,嗯……」。Smart Turn 的解法是一条全合成流水线,v2 博客把配方完整公开了:
- 句子从哪来:从开源多语言句子库出发。原始数据很脏——不合语法的、像文章标题不像口语的比比皆是,先用 Gemini 2.5 Flash 逐句分类清洗(不合语法/标点异常/语义不完整/不像口语/涉敏/语言不符),按语言不同扔掉 50–80%。
- 填充词哪里来:每种语言的「嗯」不一样——英语 “um”,日语「えーと」「あの」。用 Claude 和 GPT-o3 给 每种语言建填充词表,再加上 “and” “but” 这类几乎不可能出现在句尾的连接词。
- 怎么截成半句:早期英语数据直接在空格处随机截断,但中文日文没有空格,于是升级成让 LLM 找自然的截断点:把句子在接近结尾处剪断、接上填充词和省略号。
- 怎么读出「没说完」:这是最巧的一步。多数 TTS 读不好半句话,团队测下来 Google 的 Chirp3 例外——填充词发音自然,而且句尾放个逗号,它就会用「话没说完」的悬置语调收尾。一个标点符号,控制了整条数据管线最关键的声学特征。
- 反例教学:专门造一批填充词在句中的完整句——「我,嗯,想退货」是说完了的。不然模型会学到「有嗯就没完」的捷径。
这条流水线撑起了 v2 和 v3 的多语言能力,但 v3.1 暴露了它的天花板:TTS 合成的「犹豫」终究是演的,缺少真人语音里的自然变化和微妙线索。v3.1 引入真人录音后,英语准确率从 88.3% 跳到 94.7%——注意这个 88.3% 和 v3 发布时报的 94.31% 不矛盾:前者是 v3.0 模型在掺入真人数据的新测试集上的成绩。换一个更接近真实的考卷,旧模型立刻掉了 6 个点,这本身就是「合成数据训练、合成数据评测」这个闭环的自我暴露(上一篇讲的评测口径问题,在一个 8 MB 的模型上原样复现)。
项目现在把「攒真人数据」做成了社区工程:一个数据标注站让志愿者逐条听样本剔除脏数据,一组轮次训练小游戏让你边玩边贡献录音。训练集和测试集全部公开在 HuggingFace 上(smart-turn-data-v3.2-train/-test),任何人可以复现训练和评测。
五、装进语音 agent:VAD 扣扳机,Smart Turn 做裁决
单看模型只是半个故事——它在真实 agent 里怎么用,藏在 Pipecat 的源码里,而且设计得相当漂亮:VAD 和 Smart Turn 不是二选一,是双保险。
按源码里的默认参数走一遍时序:
- Silero VAD 持续标注每一帧有没有人声(默认置信度 0.7,起说 0.2 秒、停说 0.2 秒)。Smart Turn 不需要一直跑——只在停顿出现时才需要裁决。
- 用户停顿 0.2 秒,VAD 报告「停止说话」,触发一次 Smart Turn 推理:取本轮开始前 500 毫秒(
pre_speech_ms)至今的整段音频,截最后 8 秒,喂给模型。 - 模型说 complete:回合结束,下游立刻接话。配合流式 ASR,这就是 0.2 秒静音 + 十几毫秒推理的响应速度——上一篇「零间隙衔接」格子需要的语义预判,这里就是实现。
- 模型说 incomplete:什么都不做,继续听。用户若接着说,等下一次停顿再重新裁决——而且每次都拿整轮音频从头判一遍,不是只看新片段(模型需要完整上下文,README 明确不建议喂很短的音频段)。
- 兜底:用户若在「incomplete」裁决后一直沉默,不会再触发新的推理——静音累计到
stop_secs = 3秒时,不管模型怎么说都强制结束回合。模型判断再准也可能遇到分布外的输入,一个纯计时器兜住最坏情况——这是把 ML 组件放进生产系统的标准姿势。
工程细节也见功力:Pipecat 直接把 v3.2 的 CPU 权重打包进了 pip 包里,LocalSmartTurnAnalyzerV3 默认零配置可用;特征提取没有引入 torch/transformers 依赖,而是用 numpy 重写了 Whisper 的 log-mel 计算;管线采样率不是 16 kHz 时自动重采样;设一个环境变量(PIPECAT_SMART_TURN_LOG_DATA)就能把每次判定的音频落盘成 wav,按 complete/incomplete 分类——现成的坏例采集器,攒自己的微调数据正好用它。
想脱离 Pipecat 单独玩也很简单,仓库里有现成脚本:
git clone https://github.com/pipecat-ai/smart-turn
cd smart-turn
python3.12 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python record_and_predict.py
对着麦克风分别说「我好像没办法,嗯……」和「我好像没办法找到退货标签了」,看两种停顿得到的概率差。
六、横向对比、准确率与边界
v3 发布时官方给了一张与同类原生音频判停模型的对比(数字截至 2025 年 9 月):
| Smart Turn v3 | Krisp | Ultravox | |
|---|---|---|---|
| 体积 | 8 MB | 65 MB | 1.37 GB |
| 语言 | 23 种 | 仅英语训练/测试 | 26 种 |
| 开放程度 | 权重 + 数据 + 训练代码 | 专有 | 开放权重 |
| 架构取向 | 单次推理的裁决时延 | 多次推理堆置信度 | 结合对话上下文 |
逐语言准确率(v3.0 发布时的官方测试集,节选,完整 23 种见官方博客):
| 语言 | 准确率 | 语言 | 准确率 |
|---|---|---|---|
| 土耳其语 | 97.10% | 俄语 | 93.67% |
| 韩语 | 96.85% | 印地语 | 93.44% |
| 日语 | 96.76% | 西班牙语 | 91.97% |
| 德语 | 96.14% | 阿拉伯语 | 88.60% |
| 法语 | 96.01% | 中文 | 88.57% |
| 英语 | 94.31% | 越南语 | 81.27% |
两点提醒。其一,中文准确率在 23 种语言里排倒数第四(低于它的只有越南语、孟加拉语和马拉地语)——中文数据主要靠合成管线,还没吃到 v3.1 那波真人数据红利(那批只有英语和西班牙语);生产上用在中文场景,值得拿自己的音频先测一轮,或者干脆用开源的训练脚本微调。其二,这些数字来自各版本各自的测试集,纵向不可直接比——判停模型的「准确率」永远要连着「什么测试集」一起读。
最后是边界,用上一篇的六个试金石对号入座:Smart Turn 打的是「犹豫长停顿」这一格(外加给「零间隙衔接」提供语义预判能力)。它只在用户停顿后运行,回答「说完了没」这一个问题,所以:附和「嗯嗯」不打断(需要意图分类)、旁人说话不接茬(需要说话人过滤)、说话中被抢话(打断处理走 VAD 的另一条通路)——都不归它管。README 也列了下一步方向,最有意思的是文本条件化:让模型「知道」用户正在报信用卡号或电话号码,从而在数字串中间的停顿上更有耐心。这本质上是往模型里注入对话状态——一个 8 MB 组件正在朝上一篇 L0 层的天花板轻轻撞头。
七、结语
Smart Turn 值得写一篇的原因,不只是「有用的小工具」:
- 它是小模型哲学的教科书案例。判停不需要世界知识,不需要生成能力,只需要听懂「没说完」的声音形状——所以 8 MB 够了,CPU 够了,十几毫秒够了。任务定义得足够窄,模型才能足够小;模型足够小,才能塞进每一个 agent 实例里白送。
- 它把「开源」做全了。权重、数据、训练代码、评测脚本、数据清洗工具、社区标注站,一条链全开放。上一篇说全双工领域「公开的双流对话数据全球只有五千小时」,Smart Turn 的社区攒数据模式——标注站 + 小游戏 + 数据公司合作——是对这个瓶颈的一次具体回应,虽然它攒的只是判停这一格的数据。
- 它的三代演进是一份免费的实验报告:更大的预训练底座会过拟合小任务;换底座比调超参收益大;真人数据比合成数据值钱;测试集换血时准确率数字会「跳水」——每一条都有公开数字背书。
回到开头那个抢答的助手:给它加一个 Smart Turn,代价是每次停顿多花十几毫秒 CPU 时间,换来的是「我想要……那个……」之后的一秒安静。对话体验里最贵的东西,往往就是这一秒的耐心。
参考
- 仓库:github.com/pipecat-ai/smart-turn(README、
train.py、inference.py);权重:HuggingFace pipecat-ai/smart-turn-v3 - 官方博客:Smart Turn v2(合成数据流水线细节)、v3(架构与 CPU 推理)、v3.1(真人数据)、v3.2(短句与噪声)
- Pipecat 集成源码:base_smart_turn.py、local_smart_turn_v3.py
- 姊妹篇:不是「能被打断」就算全双工:三张地图读懂语音对话系统