PersonaPlex

Modelo de diálogo voz a voz full-duplex basado en la arquitectura Moshi (Kyutai). PersonaPlex 7B genera respuestas habladas directamente a partir de la entrada hablada — sin pipeline intermedio de texto. El modelo incluye 18 presets de voz y está disponible con cuantización de 8 bits (recomendada) y 4 bits. 8 bits es el valor por defecto — es un 30 % más rápido y produce respuestas coherentes, mientras que 4 bits degrada la calidad de la salida.

Arquitectura

PersonaPlex es un modelo autorregresivo multi-stream con tres componentes principales:

ComponenteDetalles
Temporal Transformer32 capas, dim=4096, 32 cabezas, SwiGLU (hidden_scale=4.125), RoPE, cuantizado a 8 bits (por defecto)
Depformer6 capas, dim=1024, 16 cabezas, MultiLinear (weights_per_step=true), dep_q=16
Codec Mimi16 codebooks, frame rate de 12.5 Hz, salida de audio a 24 kHz

El modelo procesa 17 streams simultáneamente: 1 stream de texto + 8 streams de audio del usuario + 8 streams de audio del agent. Esta arquitectura permite la conversación full-duplex en la que el modelo puede escuchar y hablar al mismo tiempo.

Presets de voz

PersonaPlex incluye 18 presets de voz integrados con estilos naturales y variados:

CategoríaPresets
Femenino naturalNATF0, NATF1, NATF2, NATF3
Masculino naturalNATM0, NATM1, NATM2, NATM3
Femenino variadoVARF0, VARF1, VARF2, VARF3, VARF4
Masculino variadoVARM0, VARM1, VARM2, VARM3, VARM4

Monólogo interno

PersonaPlex genera dos streams paralelos en cada paso: 8 tokens de codebook de audio para el codec Mimi y un token de texto para el monólogo interno del modelo. El stream de texto es lo que el modelo está “pensando” mientras habla — puede divergir ligeramente del audio final, pero en la práctica refleja la respuesta hablada con suficiente fidelidad como para usarlo como transcripción en vivo.

Los tokens de texto se devuelven como IDs de piezas SentencePiece en crudo. Decodifícalos con el SentencePieceDecoder que incluye PersonaPlex:

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

En modo streaming, respondStream emite chunks de textTokens según se van produciendo — decodifícalos de forma incremental para alimentar una vista de subtítulos en vivo mientras el audio sigue generándose. El flag --transcript de la CLI hace exactamente esto por detrás.

Por qué importa: SentencePieceDecoder está construido sobre el lector de protobuf compartido AudioCommon.SentencePieceModel, así que PersonaPlex, OmnilingualASR y cualquier futuro modelo basado en SentencePiece decodifican a través de la misma implementación del tokenizador. Consulta la referencia de SentencePieceModel.

Prompts del sistema

Pasa cualquier prompt del sistema personalizado como una simple cadena de texto — no hace falta tokenización externa:

let response = model.respond(
    userAudio: audio,
    voice: .NATM0,
    systemPrompt: "You enjoy having a good conversation."
)

O usa un preset integrado:

Uso de la CLI

Genera una respuesta hablada a partir de una entrada de audio:

# Basic speech-to-speech
.build/release/audio respond --input question.wav

# Choose a voice preset
.build/release/audio respond --input question.wav --voice NATM0

# Stream audio output during generation
.build/release/audio respond --input question.wav --stream

# Custom system prompt text
.build/release/audio respond --input question.wav --system-prompt-text "You enjoy having a good conversation."

# Use a preset system prompt
.build/release/audio respond --input question.wav --system-prompt customer-service

# Get transcript alongside audio
.build/release/audio respond --input question.wav --transcript

# JSON output with metadata
.build/release/audio respond --input question.wav --json

Opciones

OpciónDescripción
--inputArchivo de audio de entrada (WAV, obligatorio)
--voiceNombre del preset de voz (p. ej., NATM0, VARF2)
--system-promptPreset de prompt del sistema: assistant, focused, customer-service, teacher
--system-prompt-textTexto personalizado del prompt del sistema (sobrescribe --system-prompt)
--max-stepsPasos máximos de generación
--streamEmite chunks de audio durante la generación
--compileUsa inferencia compilada de MLX para una generación más rápida
--transcriptEmite la transcripción de texto junto al audio
--jsonSalida JSON con metadatos

Los parámetros de sampling también se pueden sobrescribir:

OpciónPor defectoDescripción
--audio-temp0.8Temperatura de sampling de tokens de audio
--audio-top-k250Top-k sampling para tokens de audio
--text-temp0.7Temperatura de sampling de tokens de texto
--text-top-k25Top-k sampling para tokens de texto

Streaming

El flag --stream activa la salida de audio en tiempo real. Los chunks de audio se emiten conforme se generan, por lo que la reproducción puede comenzar antes de que la respuesta completa esté lista. Esto resulta especialmente útil para aplicaciones interactivas donde la baja latencia importa.

Rendimiento

MétricaValor
Factor de tiempo real (RTF)~1.4 (8 bits, casi en tiempo real)
Latencia por paso~112 ms/paso en M2 Max (8 bits)
Tamaño del modelo (8 bits)~9.1 GB
RAM máxima (8 bits)~11 GB
Tamaño del modelo (4 bits)~4.9 GB
RAM máxima (4 bits)~7 GB
Importante

PersonaPlex 7B (8 bits) requiere al menos 24 GB de RAM. La variante de 4 bits cabe en dispositivos con 16 GB pero produce una salida degradada. En dispositivos con 8 GB no cabe ninguna de las variantes. Usa --compile para obtener el mejor rendimiento en el hardware compatible.

Variantes del modelo

ModeloTamañoHuggingFace
PersonaPlex-7B (8 bits) recomendado9.1 GBaufklarer/PersonaPlex-7B-MLX-8bit
PersonaPlex-7B (4 bits)4.9 GBaufklarer/PersonaPlex-7B-MLX-4bit

API de Swift

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")