参数写错了不报错:非激活后端的选项只会带个警告被忽略
命令行工具里最难查的一类问题,不是报错,是不报错。
speech-to-speech 的 README 在「Supported Components」那一节末尾写了一句话,很短,但它是这个项目里最值得贴在显示器边上的一条行为约定:CLI 只为选中的后端构造配置;未激活后端的已知选项仍然被接受,但会带警告地忽略。 JSON 配置同理,多余的非激活后端键同样被忽略。
翻译成人话就是:你把某个后端的专属参数写在命令行上,而那个后端这次压根没被启用,程序不会拒绝你,不会退出,只会警告一声然后照跑。
一、现象长什么样
这类问题的共同特征是「命令没有拒绝你,但你要的配置没有被构造出来」。
具体表现通常是三步:你查文档翻到一个参数,加进 speech-to-speech serve 后面,进程正常启动,没有 unrecognized arguments 这类硬错误;然后你发现行为和不加这个参数时没有区别;再然后你开始怀疑参数名拼错了、版本不对、文档过期了,于是往完全错误的方向查半天。
而真正的原因往往只是:这个参数属于另一个后端,而这次运行没有激活它。
要理解为什么这么容易踩,得先记住三个选择器的默认值(来自 module_arguments.py):
--stt默认parakeet-tdt--llm_backend默认responses-api--tts默认qwen3
也就是说,你什么都不写的时候,激活的是 Parakeet TDT + OpenAI 兼容 Responses API + Qwen3-TTS 这一套。你从文档里抄来的任何 Whisper 参数、Kokoro 参数、Pocket TTS 参数、本地 LLM 参数,都不在这次的激活集合里。
二、怎么确认就是这个问题
三个可执行动作,按顺序做。
动作一:看你写的参数前缀属于谁。 仓库的 src/speech_to_speech/arguments_classes/ 下是 18 个 dataclass 参数文件,共 131 个 CLI 参数。参数前缀和文件几乎是一一对应的,这张对照表只列最容易踩的几行:
| 你写的参数 | 定义在 | 需要哪个选择器激活 |
|---|---|---|
--kokoro_voice、--kokoro_lang_code | kokoro_tts_arguments.py | --tts kokoro |
--pocket_tts_voice、--pocket_tts_device | pocket_tts_arguments.py | --tts pocket |
--qwen3_tts_speaker 等 23 个 | qwen3_tts_arguments.py | --tts qwen3(默认已激活) |
--stt_model_name、--stt_device | whisper_stt_arguments.py | --stt 选 Whisper 系 |
--faster_whisper_stt_* | faster_whisper_stt_arguments.py | --stt 选 Faster Whisper |
--parakeet_tdt_model_name、--parakeet_tdt_language | parakeet_tdt_arguments.py | --stt parakeet-tdt(默认已激活) |
--llm_device、--llm_torch_dtype、--llm_gen_temperature | language_model_arguments.py | --llm_backend transformers 或 mlx-lm |
--responses_api_base_url 等 | responses_api_language_model_arguments.py | --llm_backend responses-api(默认)或 chat-completions |
--responses_api_reasoning_effort | chat_completions_language_model_arguments.py | --llm_backend chat-completions |
最后一行是个陷阱:--responses_api_reasoning_effort 顶着 responses_api 的前缀,但它单独定义在 chat completions 那个文件里。README 讲「什么时候该改用 chat-completions」时给的正是这个旋钮——它是为 chat completions 路径准备的。光看前缀猜归属,这里就会猜错。
动作二:用带选择器的 -h 看这次组合到底认哪些参数。 README 明确给了用法:查看某个组合的默认值与专属参数用
speech-to-speech serve -h
要看另一种组合的参数,把选择器放在 -h 前面:
speech-to-speech serve --stt mlx-audio-whisper -h
这条用法是整个排查动作里最关键的一步。它不是可选的技巧,而是这个 CLI 的设计使然:帮助文本本身就是按激活后端动态组装的。你的参数如果没出现在这次组合的 -h 输出里,它这次就不会进配置。
动作三:把日志级别调高一点。 README 说被忽略时会带一个警告,那条警告能不能被你看到,取决于日志级别。--log_level 默认 info,它的 help 文本给的例子就是 --log_level debug。这属于顺手做的一步,真正下结论还是靠动作二的 -h 输出。
三、为什么同一个参数名会有两个默认值
有一个现象能反过来印证「配置是按后端构造的」这件事:--model_name 在两个文件里都有定义。
language_model_base_arguments.py 里它默认是 Qwen/Qwen3-4B-Instruct-2507;responses_api_language_model_arguments.py 里它默认是 gpt-5.4-mini。README 给出的默认等价命令里写的是 --model_name gpt-5.4-mini,因为默认 --llm_backend 就是 responses-api。
所以「--model_name 的默认值是多少」这个问题,脱离了 --llm_backend 是没有答案的。这也解释了为什么选择器必须放在 -h 前面——不先告诉它你要哪个后端,它没法告诉你默认值。
四、两个最典型的踩法
第一个:想调温度。 --llm_gen_temperature 默认 0.0、--llm_gen_do_sample 默认 False,这两个参数定义在 language_model_arguments.py,服务的是 transformers 与 mlx-lm 这类进程内本地推理后端。默认 --llm_backend 是 responses-api,走的是 OpenAI 兼容 API 那条路,这两个参数不在激活集合里。
第二个,对中文读者尤其要命:想做中文语音代理。 --language 定义在 whisper_stt_arguments.py 与 mlx_audio_whisper_arguments.py 里,默认值是 en;而默认 STT 是 Parakeet TDT,它自己的语言参数叫 --parakeet_tdt_language,且这个后端按 README 的多语言表覆盖的是 25 种欧洲语言。
换句话说,只加一个 --language zh 而不换 STT,这件事从根上就不成立——不是参数被忽略那么简单,是默认 STT 这条路上就没有中文。README 给的中文示例是把 STT 一起换掉的:
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
注意这条命令里 --stt whisper-mlx 和 --stt_model_name 是配套出现的:先激活 Whisper MLX,--stt_model_name 才有归属。
五、处置:要么补选择器,要么换成激活后端的对应参数
处置只有两条路。
路一,补上选择器。 比如你想用 Kokoro 的音色,只写音色是不够的:
# 不对:--tts 仍是默认的 qwen3,Kokoro 的参数不在激活集合里
speech-to-speech serve --kokoro_voice bm_fable
# 对:先把 TTS 换成 kokoro
speech-to-speech serve --tts kokoro --kokoro_voice bm_fable
以上为按官方参数语义组合的示例,未逐项实测,以官方文档与 --help 的实际输出为准。另外别忘了非 macOS 平台上 Kokoro 是可选组件,README 给的装法是 pip install "speech-to-speech[kokoro]";macOS 上它是内置的。
路二,换成当前激活后端里功能对应的那个参数。 想指定 STT 模型而你用的是默认的 Parakeet TDT,那要写的是 --parakeet_tdt_model_name,不是 --stt_model_name;想指定 Parakeet 的目标语言,写 --parakeet_tdt_language。
JSON 配置同理。 README 说得很清楚,配置文件里多余的非激活后端键同样会被忽略。这里给一条判断依据:命令行至少每次都摊在你眼前,一份从别处继承来的大而全的 JSON 反而更危险——里面可能积着好几套后端的键,而你永远看不出哪些这次没生效。所以维护配置文件时,最好按当前组合裁剪,而不是留着「以后换后端就能用」的死键。
六、处置后拿什么验证
第一,再跑一次带选择器的 -h,确认三件事:你的参数出现在这次组合的输出里;它显示的默认值来自你预期的那个定义文件(--model_name 那个例子就是最好的检验);你要改的值确实是这个参数管的。
第二,确认那条忽略警告不再出现。 README 描述的行为是「带警告地忽略」,反过来说,警告消失就是配置被构造进去的一个信号。
第三,别拿「行为变了」当验证依据。 这个流水线的输出是音频,人耳听感、响应快慢都不是可靠的判定手段,参数是否进入配置这件事应该在 -h 输出和警告这一层就闭环掉,不要靠体感倒推。
七、什么情况说明不是这个原因
这一节比上面几节更重要——排查最大的浪费是沿着错误方向查到底。以下几种症状看起来都像「参数没生效」,但和「后端未激活」无关。
一、参数在激活集合里,但被钳制或条件失效。 VAD 那几个参数是重灾区:
--min_speech_continuation_ms默认192,help 明写它被钳制在[100, min_speech_ms]区间;而--min_speech_ms默认384。也就是说,你给--min_speech_continuation_ms写一个 500 进去,写的值不会照原样生效,因为它超出了上界,会被钳到min_speech_ms这一侧。help 里还补了一句:这个拆分只作用于「续接一个可重开轮次」的情况,新起的一轮和打断始终按min_speech_ms判定;写0则关掉拆分、退回只用min_speech_ms。--unanswered_reopen_ms默认7000,help 写着它低于--speculative_reopen_ms(默认800)时无效,并且在 Smart Turn 启用时被钳制到--smart_turn_max_wait_ms(默认2000)。而--smart_turn默认就是开的(关掉要传--no_smart_turn)。
这类是「生效规则」问题,参数确实进了配置,只是值被规则改写了。查它要去读 help 文本里的钳制说明,不是去查后端选择器。
二、平台级忽略。 --qwen3_tts_backend 默认 ggml,但它的 help 写明在 Apple Silicon 上会自动选 mlx-audio 后端,这个选项被忽略;--qwen3_tts_non_streaming_mode 默认 True,help 写着目前在 Apple Silicon 上被忽略,因为 mlx-audio 还没暴露这个能力。这类参数即使出现在 -h 里、即使后端选对了,在特定平台上依然不起作用。
三、你用的是已废弃的 --mode。 这个开关已废弃且很快将停止工作。迁移窗口内 --mode realtime 等价于 serve、--mode local 等价于 local,两者都会打印警告;其余所有取值已被移除,会带着「改用新命令」的提示直接退出。也就是说 --mode 这条路是有明确反馈的,不属于静默忽略。网上大量旧教程用的还是它。
四、两个名字相近的开关搞混了。 --enable_live_transcription 是模块级参数,默认 True;--enable_realtime_transcription 是 VAD 侧参数,默认 False。这是两个不同的参数,你以为改的开关没生效,实际可能是改错了那一个。
五、单位搞错。 --realtime_processing_pause 的单位是秒(默认 0.5),带 _ms 后缀的那些才是毫秒。填错三个数量级,看起来也很像「没生效」。
六、名字压根不是任何已知选项。 README 那句约定覆盖的是「未激活后端的已知选项」。一个彻底拼错的参数名不属于这一类,仓库文档没有对它做出承诺,实际是报错还是别的行为,以你那个版本的实际输出为准。
七、症状是「第二个客户端连不上」。 那不是参数被忽略,是 --num_pipelines 默认为 1。它的 help 写得很直白:池里隔离的流水线实例数量,一个 uvicorn 服务监听 --port(默认 8765),把每个进来的客户端路由到下一个空闲流水线,每个流水线有自己的 VAD/STT/LM/TTS handler 与会话状态;最大并发 WebSocket 会话数等于 num_pipelines,再多的连接会被拒绝。做多用户部署时,这是第一个要动的参数。
最后
这个设计本身谈不上坏——参数命名空间按后端分开、只构造激活部分的配置,是一种很常见的取舍,硬报错反而会让「先写全再切后端」的用法变得难受。但对使用者来说,代价是排查时多了一层:你得先确定这次跑的是哪三个后端,参数才有意义。
把 speech-to-speech serve --stt <你的选择> --llm_backend <你的选择> --tts <你的选择> -h 这条动作变成肌肉记忆,比记住 131 个参数有用得多。
延伸阅读
- Qwen3-TTS 装不上:那个 wheel 默认盯的是 CUDA 12.8
- DeepFilterNet 要 numpy 小于 2,Pocket TTS 要大于等于 2
- 从 pip install 到第一次 serve:默认装了些什么
本文依据 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 的实际输出为准。