语音到语音模型
Soniqo 支持两个用于语音对话的原生 MLX 模型系列:PersonaPlex 7B 和 VoiceChat 11B。两者都接收语音并直接生成模型音频,但采用不同的双工架构和运行时约定。
选择模型
| 模型 | 对话方式 | 最适合 |
|---|---|---|
| PersonaPlex 7B | 基于 Moshi、Depformer 和 Mimi 的同步全双工 | 听说重叠和 18 种可选音色 |
| VoiceChat 11B | 基于 FastConformer、Nemotron-H、EAR-TTS 和神经编解码器的连续帧同步双工 | 80 ms 流式帧、文本/函数事件和 checkpoint 原生语音 |
这些是对话型语音到语音模型。Hibiki 也会把语音直接转换为语音,但其任务是流式语音翻译,因此单独记录。
PersonaPlex 7B
基于 Moshi 架构(Kyutai)的全双工语音到语音对话模型。PersonaPlex 7B 直接从语音输入生成语音响应——不需要中间文本流水线。模型自带 18 种预设音色,提供 8 位(推荐)和 4 位量化版本。8 位是默认选项——它快 30% 并能产生连贯的回答,而 4 位会降低输出质量。
VoiceChat 11B
双工语音到语音架构参考:SALM-Duplex: Efficient and Direct Duplex Modeling for Speech-to-Speech Language Model(Interspeech 2025),NVIDIA 上游 VoiceChat 模型卡引用了该论文。
M5 Pro、48 GB、Release、80 ms 实时节奏、保护输出头的 INT5:三次受控测试的全流程 RTF 为 0.94、0.92 和 0.92,峰值 RSS 约 8.70 GB。首个语音文本在 44.3–46.7 ms 出现,首段可播放音频在 74.0–76.4 ms 出现。这些是热态计算数据,不是模型学习到的轮次切换延迟;优化后的 INT8 性能尚未重新测量。
VoiceChat 是 speech-swift 中另一个原生 MLX 语音到语音运行时。它结合因果 FastConformer 感知、Nemotron-H 双工文本/函数通道、独立文本条件 EAR-TTS 和 22.05 kHz 神经音频编解码器。会话持续接收 16 kHz 单声道音频,并每 80 ms 返回文本事件和一个 1,764 采样的输出帧。
使用 VoiceChatModel.load(from:),启动 VoiceChatSession,再通过 pushAudio 输入音频。完整 bundle 必须包含 encoder/、llm/ 和 tts/;旧的仅理解 bundle 会被拒绝。
INT5 在测试的 M5 Pro 上保持低于 RTF 1 的总体吞吐量。三次测试的每帧总延迟 p95 均低于 80 ms,范围为 76.2–78.6 ms;因此截止时间余量仍然很小。模型轮次起点与硬件计算延迟应分别测量。
PersonaPlex 架构与用法
PersonaPlex 是一个多流自回归模型,由三个核心组件构成:
| 组件 | 细节 |
|---|---|
| Temporal Transformer | 32 层,dim=4096,32 heads,SwiGLU (hidden_scale=4.125),RoPE,默认 8 位量化 |
| Depformer | 6 层,dim=1024,16 heads,MultiLinear (weights_per_step=true),dep_q=16 |
| Mimi Codec | 16 个 codebook,12.5 Hz 帧率,24 kHz 音频输出 |
模型同时处理 17 个流:1 个文本流 + 8 个用户音频流 + 8 个 agent 音频流。这种架构实现了模型可以同时听和说的全双工对话。
预设音色
PersonaPlex 内置 18 种预设音色,涵盖自然和多样化的风格:
| 类别 | 预设 |
|---|---|
| Natural Female | NATF0, NATF1, NATF2, NATF3 |
| Natural Male | NATM0, NATM1, NATM2, NATM3 |
| Varied Female | VARF0, VARF1, VARF2, VARF3, VARF4 |
| Varied Male | VARM0, VARM1, VARM2, VARM3, VARM4 |
内部独白
PersonaPlex 在每一步都生成两个并行流:8 个用于 Mimi codec 的音频 codebook token 和一个模型内部独白的文本 token。文本流是模型在说话时"正在想"的内容——它与最终音频可能略有偏差,但实际上足够贴近口头回答,可以作为实时转写使用。
文本 token 以原始 SentencePiece piece ID 的形式返回。使用 PersonaPlex 自带的 SentencePieceDecoder 解码:
import PersonaPlex
import AudioCommon
let model = try await PersonaPlexModel.fromPretrained()
let decoder = model.tokenizer // SentencePieceDecoder?
let result = model.respond(userAudio: userSamples, voice: .NATM0)
let transcript = decoder?.decode(result.textTokens) ?? ""
print(transcript) // "Sure, I can help with that..."
playAudio(result.audio) // 24 kHz mono Float32
在流式模式下,respondStream 会在生成过程中持续发出 textTokens 块——增量解码这些块即可驱动实时字幕视图,同时音频仍在生成。CLI 的 --transcript 标志正是在背后做同样的事。
为什么重要:SentencePieceDecoder 构建在共享的 AudioCommon.SentencePieceModel protobuf reader 之上,因此 PersonaPlex、OmnilingualASR 以及任何未来基于 SentencePiece 的模型都通过同一个 tokenizer 实现进行解码。参见 SentencePieceModel 参考。
System Prompt
可以将任意自定义 system prompt 作为普通字符串传入——不需要外部 tokenization:
let response = model.respond(
userAudio: audio,
voice: .NATM0,
systemPrompt: "You enjoy having a good conversation."
)
或使用内置预设:
assistant— 通用型有帮助的助手(默认)focused— 简洁、直接的回答customer-service— 礼貌、以解决方案为导向的客服 agentteacher— 耐心、善于讲解的教学风格
CLI 使用
从音频输入生成语音响应:
# Basic speech-to-speech
.build/release/speech respond --input question.wav
# Choose a voice preset
.build/release/speech respond --input question.wav --voice NATM0
# Stream audio output during generation
.build/release/speech respond --input question.wav --stream
# Custom system prompt text
.build/release/speech respond --input question.wav --system-prompt-text "You enjoy having a good conversation."
# Use a preset system prompt
.build/release/speech respond --input question.wav --system-prompt customer-service
# Get transcript alongside audio
.build/release/speech respond --input question.wav --transcript
# JSON output with metadata
.build/release/speech respond --input question.wav --json
选项
| 选项 | 说明 |
|---|---|
--input | 输入音频文件(WAV,必填) |
--voice | 音色预设名(如 NATM0、VARF2) |
--system-prompt | system prompt 预设:assistant、focused、customer-service、teacher |
--system-prompt-text | 自定义 system prompt 文本(会覆盖 --system-prompt) |
--max-steps | 最大生成步数 |
--stream | 在生成过程中持续输出音频 chunk |
--compile | 使用 MLX 编译推理以更快生成 |
--transcript | 在音频之外输出文本转写 |
--json | 带元数据的 JSON 输出 |
采样参数也可覆盖:
| 选项 | 默认值 | 说明 |
|---|---|---|
--audio-temp | 0.8 | 音频 token 采样温度 |
--audio-top-k | 250 | 音频 token top-k 采样 |
--text-temp | 0.7 | 文本 token 采样温度 |
--text-top-k | 25 | 文本 token top-k 采样 |
流式
--stream 标志启用实时音频输出。音频 chunk 在生成时即被输出,因此在完整响应完成之前就可以开始播放。这对低延迟非常重要的交互式应用特别有用。
性能
| 指标 | 值 |
|---|---|
| 实时因子 (RTF) | ~1.4(8 位,接近实时) |
| 单步延迟 | M2 Max 上约 112 ms/step(8 位) |
| 模型大小(8 位) | ~9.1 GB |
| 峰值内存(8 位) | ~11 GB |
| 模型大小(4 位) | ~4.9 GB |
| 峰值内存(4 位) | ~7 GB |
PersonaPlex 7B(8 位)至少需要 24 GB 内存。4 位变体可在 16 GB 设备上运行,但输出质量会下降。在 8 GB 设备上,两种变体都无法放下。在支持的硬件上使用 --compile 可获得最佳性能。
模型变体
| 模型 | 大小 | HuggingFace |
|---|---|---|
| PersonaPlex-7B (8 位) 推荐 | 9.1 GB | aufklarer/PersonaPlex-7B-MLX-8bit |
| PersonaPlex-7B (4 位) | 4.9 GB | aufklarer/PersonaPlex-7B-MLX-4bit |
Swift API
import PersonaPlex
import AudioCommon
let model = try await PersonaPlexModel.fromPretrained()
let response = model.respond(
userAudio: userSamples,
voice: .NATM0,
systemPrompt: "You are a helpful assistant."
)
try WAVWriter.write(samples: response.audio, sampleRate: 24000, to: URL(filePath: "answer.wav"))