PersonaPlex
Модель полнодуплексного диалога речь-в-речь на базе архитектуры Moshi (Kyutai). PersonaPlex 7B генерирует голосовые ответы непосредственно из голосового входа — без промежуточного текстового пайплайна. Модель поставляется с 18 голосовыми пресетами и доступна в 8-битной (рекомендуется) и 4-битной квантизации. По умолчанию 8-bit — она на 30% быстрее и выдаёт связные ответы, тогда как 4-bit ухудшает качество вывода.
Архитектура
PersonaPlex — это многопоточная авторегрессивная модель с тремя ключевыми компонентами:
| Компонент | Детали |
|---|---|
| Temporal Transformer | 32 слоя, dim=4096, 32 head, SwiGLU (hidden_scale=4.125), RoPE, 8-bit-квантизация (по умолчанию) |
| Depformer | 6 слоёв, dim=1024, 16 head, MultiLinear (weights_per_step=true), dep_q=16 |
| Mimi Codec | 16 codebook, частота кадров 12.5 Гц, аудио-выход 24 кГц |
Модель обрабатывает 17 потоков одновременно: 1 текстовый поток + 8 пользовательских аудиопотоков + 8 аудиопотоков агента. Эта архитектура позволяет полнодуплексный диалог, где модель может одновременно слушать и говорить.
Голосовые пресеты
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 audio-codebook-токенов для Mimi-кодека и один текстовый токен для внутреннего монолога модели. Текстовый поток — это то, что модель «думает», пока говорит — он может немного расходиться с финальным аудио, но на практике достаточно близко повторяет голосовой ответ, чтобы использовать его как живой транскрипт.
Текстовые токены возвращаются как сырые SentencePiece piece ID. Декодируйте их через SentencePieceDecoder, который поставляется в 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 кГц моно Float32
В потоковом режиме respondStream выдаёт куски textTokens по мере их появления — декодируйте их инкрементально, чтобы обновлять живые субтитры, пока аудио всё ещё генерируется. Флаг CLI --transcript делает именно это за кулисами.
Почему это важно: SentencePieceDecoder построен на общем AudioCommon.SentencePieceModel-ридере protobuf, так что PersonaPlex, OmnilingualASR и любые будущие модели на SentencePiece декодируют через одну реализацию токенизатора. См. справочник SentencePieceModel.
Системные промпты
Передайте любой пользовательский системный промпт как обычную строку — внешняя токенизация не нужна:
let response = model.respond(
userAudio: audio,
voice: .NATM0,
systemPrompt: "You enjoy having a good conversation."
)
Или используйте встроенный пресет:
assistant— Универсальный помощник общего назначения (по умолчанию)focused— Краткие, прямые ответыcustomer-service— Вежливый агент поддержки, ориентированный на решениеteacher— Терпеливый, объясняющий стиль преподавания
Использование 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
# Пользовательский системный промпт
.build/release/audio respond --input question.wav --system-prompt-text "You enjoy having a good conversation."
# Использовать пресетный системный промпт
.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 | Пресет системного промпта: assistant, focused, customer-service, teacher |
--system-prompt-text | Пользовательский текст системного промпта (переопределяет --system-prompt) |
--max-steps | Максимум шагов генерации |
--stream | Выдавать аудио-чанки во время генерации |
--compile | Использовать compiled-инференс MLX для более быстрой генерации |
--transcript | Выводить текстовый транскрипт вместе с аудио |
--json | JSON-вывод с метаданными |
Параметры сэмплирования также можно переопределить:
| Опция | По умолчанию | Описание |
|---|---|---|
--audio-temp | 0.8 | Температура сэмплирования audio-токенов |
--audio-top-k | 250 | Top-k сэмплирование audio-токенов |
--text-temp | 0.7 | Температура сэмплирования текстовых токенов |
--text-top-k | 25 | Top-k сэмплирование текстовых токенов |
Потоковый режим
Флаг --stream включает вывод аудио в реальном времени. Аудио-чанки выдаются по мере их генерации, так что воспроизведение может начаться до того, как полный ответ будет готов. Это особенно полезно для интерактивных приложений, где важна низкая задержка.
Производительность
| Метрика | Значение |
|---|---|
| Real-time factor (RTF) | ~1.4 (8-bit, почти реальное время) |
| Задержка шага | ~112 мс/шаг на M2 Max (8-bit) |
| Размер модели (8-bit) | ~9.1 ГБ |
| Пиковая RAM (8-bit) | ~11 ГБ |
| Размер модели (4-bit) | ~4.9 ГБ |
| Пиковая RAM (4-bit) | ~7 ГБ |
PersonaPlex 7B (8-bit) требует минимум 24 ГБ RAM. Вариант 4-bit помещается на устройства с 16 ГБ, но выдаёт ухудшенный вывод. На устройствах с 8 ГБ ни один вариант не поместится. Используйте --compile для лучшей производительности на поддерживаемом оборудовании.
Варианты модели
| Модель | Размер | HuggingFace |
|---|---|---|
| PersonaPlex-7B (8-bit) рекомендуется | 9.1 ГБ | aufklarer/PersonaPlex-7B-MLX-8bit |
| PersonaPlex-7B (4-bit) | 4.9 ГБ | 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")