PersonaPlex
基于 Moshi 架构(Kyutai)的全双工语音到语音对话模型。PersonaPlex 7B 直接从语音输入生成语音响应——不需要中间文本流水线。模型自带 18 种预设音色,提供 8 位(推荐)和 4 位量化版本。8 位是默认选项——它快 30% 并能产生连贯的回答,而 4 位会降低输出质量。
架构
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 = try model.makeTextDecoder() // SentencePieceDecoder
let result = model.respondWithTranscript(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 使用
从音频输入生成语音响应:
# 基本语音到语音
.build/release/audio respond --input question.wav
# 选择音色预设
.build/release/audio respond --input question.wav --voice NATM0
# 生成过程中流式输出音频
.build/release/audio respond --input question.wav --stream
# 自定义 system prompt 文本
.build/release/audio respond --input question.wav --system-prompt-text "You enjoy having a good conversation."
# 使用预设的 system prompt
.build/release/audio respond --input question.wav --system-prompt customer-service
# 同时获取转写
.build/release/audio respond --input question.wav --transcript
# 带元数据的 JSON 输出
.build/release/audio 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
let model = try await PersonaPlexModel.loadFromHub()
let response = try await model.respond(
audioFile: "question.wav",
voice: .NATM0,
systemPrompt: .assistant
)
try response.audio.write(to: "answer.wav")