⚠ Перевод устарел

Актуальную информацию о вызовах инструментов и производительности VoiceChat см. в английской версии.

Модели речь-в-речь

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-bit — она на 30% быстрее и выдаёт связные ответы, тогда как 4-bit ухудшает качество вывода.

VoiceChat 11B

Архитектурная ссылка для дуплексного speech-to-speech: SALM-Duplex: Efficient and Direct Duplex Modeling for Speech-to-Speech Language Model (Interspeech 2025), указанная в upstream-карточке VoiceChat NVIDIA.

M5 Pro, 48 ГБ, Release, live-такт 80 мс, INT5 с защищёнными головами: три контролируемых запуска дали RTF всего пайплайна 0.94, 0.92 и 0.92 при пиковом RSS около 8.70 GB. Первый произнесённый текст появился через 44.3–46.7 ms, а первое готовое к воспроизведению аудио — через 74.0–76.4 ms. Это прогретые вычислительные значения, а не выученная задержка смены реплики; оптимизированная INT8 ещё не перемерена.

VoiceChat — второй нативный MLX-рантайм речь-в-речь в speech-swift. Он объединяет каузальное восприятие FastConformer, дуплексные текстовый/функциональный каналы Nemotron-H, автономный текстово-обусловленный EAR-TTS и нейронный кодек 22,05 кГц. Сессия непрерывно принимает моно-аудио 16 кГц и каждые 80 мс возвращает текстовые события и выходной кадр на 1 764 отсчёта.

Загрузите модель через VoiceChatModel.load(from:), запустите VoiceChatSession и передавайте аудио с pushAudio. Полный bundle должен содержать encoder/, llm/ и tts/; старые bundles только для понимания отклоняются.

Текущая производительность

INT5 на протестированном M5 Pro удерживает среднюю пропускную способность ниже RTF 1. Во всех трёх запусках общий p95 на кадр также был ниже 80 ms и составил 76.2–78.6 ms; поэтому запас по срокам остаётся небольшим. Начало реплики модели и вычислительную задержку оборудования следует измерять отдельно.

Интерактивный CLI VoiceChat

Разговаривайте с Soniqo через полный protected-head INT5 bundle на базе NVIDIA Nemotron VoiceChat 11B. Команда объединяет запись и воспроизведение в одном AVAudioEngine для Apple AEC, выводит потоковые пользовательские субтитры RNN-T из bundle и проигрывает модельный звук 22,05 кГц, продолжая слушать.

Стандартный prompt Soniqo чётко ограничивает возможности: CLI умеет вести диалог и отвечать на вопросы, но не имеет доступа к календарям, напоминаниям, приложениям, учётным записям, устройствам или внешним сервисам. На такие просьбы Soniqo прямо отвечает, что не может выполнить действие, не просит подтверждения, которое не сможет реализовать, и кратко предлагает, что пользователь может сделать сам.

speech voice-chat

speech voice-chat --input question.wav --output response.wav

speech voice-chat \\
  --mcp-config Examples/VoiceChatMCP/apple-reminders.json

Настраиваемые инструменты MCP

Встроенный адаптер apple-reminders-eventkit теперь показывает модели только list_reminders, create_reminder и update_reminder. Поиск списков остаётся внутренней операцией адаптера. Для обновления пользователь произносит название напоминания, а runtime сопоставляет его с доверенным закрытым ID EventKit; этот ID никогда не попадает в видимый модели результат. Создание и срок по-прежнему записываются одним сохранением EventKit. На первый запуск сервера даётся не менее 60 секунд, обычный вызов по умолчанию ограничен 15 секундами.

MCP включается только при наличии --mcp-config. Независимый от поставщика JSON может запустить любой stdio-сервер MCP и явно выбирает не более пяти инструментов через enabledTools. Любой инструмент вне readOnlyTools считается записывающим. По умолчанию действует confirm: первый вызов ожидает, а детерминированное подтверждение Soniqo повторяет распознанные аргументы и сообщает, что пока ничего не изменено. Только новая утвердительная реплика пользователя выполняет вызов один раз. Без новой речи пользователя одинаковый вызов нельзя выполнить дважды даже при allow. Также доступны deny и явное разрешение сеанса allow. Реальный результат возвращается по функциональному каналу модели, а сообщение об успехе требует ответа ok.

До подтверждения аргументы записи сверяются с расшифровкой речи пользователя. Относительные даты вычисляются от текущей локальной даты; выдуманные имена списков, устаревшие даты, приоритеты и другие непроверенные значения не выполняются. Новая речь RNN-T с последующим концом реплики обрабатывает подтверждение даже после сброса расшифровки. Soniqo сообщает об успехе только когда MCP-сервер действительно возвращает ok: true; иначе он говорит, что выполнение подтвердить не удалось.

Запрос «Какие у меня напоминания?» выполняет один плоский вызов list_reminders сразу по всем спискам EventKit. Список, срок, состояние выполнения и закрытый стабильный ID кэшируются, поэтому для уточнений и безопасных обновлений не нужно перечитывать каждый список. Панель отдельно показывает время, число token-шагов и token/s декодирования функции; аудио RTF по-прежнему измеряет только фоновые 80-мс кадры микрофона.

Голосовое подтверждение спрашивает, хочет ли пользователь «продолжить», и намеренно не произносит ни одной разрешающей фразы, поэтому эхо воспроизведения не может авторизовать ожидающее действие.

Дочерние MCP-серверы наследуют только PATH, HOME, временный каталог и переменные локали. Учётные данные нужно явно передать в карте env сервера; остальные секреты родительского процесса не пересылаются.

8-битная функциональная проекция занимает около 518 MB. Во время обычной речи VoiceChat вычисляет только кэшированную пробу PAD/начала инструмента размером 36 KB. Полная голова из 131 072 строк запускается лишь когда начало инструмента сначала превосходит PAD, затем проверяет глобальный argmax и остаётся активной при генерации открытого JSON-вызова. Когда результат MCP уже известен, VoiceChat воспроизводит его ограниченными причинными prefill-блоками по 16 токенов, сохраняя обученную обратную связь функции и состояние языкового и речевого кэшей без отдельного 11B-декодирования каждого токена результата. Это сохраняет разговор MCP в реальном времени без ослабления решений о начале инструмента.

По умолчанию перед воспроизведением буферизуются три кадра по 80 мс (240 мс). После разрыва воспроизведение ждёт восстановительный запас из восьми кадров. После первого callback длительностью 88 мс или трёх кадров в очереди детализация снижается с восьми до двух шагов; при 120 мс, шести кадрах или ресинхронизации — сразу до одного шага. Если очередь всё же достигает восьми кадров (640 мс), отбрасывается только минимально необходимая часть старого звука, чтобы принять новый ввод. Продолжительная перегрузка считается одним эпизодом восстановления, а уже подтверждённая RNN-T реплика пользователя остаётся активной и не стирается повторными сбросами. Пропущенные слова остаются отмеченными и могут потребовать повтора.

Метрики описывают пользовательский опыт: behind 0.3 s — ожидающий звук микрофона, RTF 0.93× — время вычисления, делённое на время звука (ниже 1 система успевает, выше 1 отстаёт), а last 74 ms for 80 ms audio сравнивает последнее время вычисления с 80 мс звука в одном кадре модели. Скользящее среднее использует последние 120 вызовов микрофона; события replay не считаются дополнительным временем звука.

В режиме микрофона по умолчанию используется управление очередностью RNN-T от NVIDIA. Обученные решения BOS/EOS модели остаются обычным низколатентным путём. Первая гипотеза каждого 80-мс кадра считается blank или non-blank; после подтверждения речи пользователя 40 последовательных blank-кадров (3,2 с) служат только страховочным запуском ответа, а 40 новых non-blank-кадров — соответствующим резервом для прерывания. Счётчик речи сбрасывается при начале ответа Soniqo, поэтому активность завершённой реплики пользователя не может сразу его оборвать. Все аудиокадры продолжают поступать в дуплексную модель; --no-rnnt-turn-taking отключает эти страховочные правила и полностью оставляет решения BOS/EOS обученной языковой голове. Панель показывает RNN-T barge-in отдельно от разрывов воспроизведения.

В обычном режиме модельный BOS подавляется до подтверждения речи пользователя, поэтому Soniqo не начинает разговор первой. --greet явно разрешает начальное приветствие. После слышимого содержания ответа blank-PAD остаются в открытой реплике как минимум 16 кадров или по три кадра на каждый содержательный token — выбирается большее значение. Первый blank-PAD сверх этого бюджета закрывает логическую реплику. Такой хвост, зависящий от длины ответа, сохраняет поздние слова, которые фиксированное ограничение в 1,28 секунды может заглушить.

В интерактивном режиме используется фиксированная панель на альтернативном экране терминала: обновления перерисовываются на месте и не заполняют историю. Для дописываемого вывода используйте --plain.

Для локальной диагностики --debug-timeline ставит временную метку каждой фразе пользователя и Soniqo и добавляет в общую хронологию разобранные аргументы нативного вызова, начало/завершение MCP и синхронизацию кэша результата. Он также отмечает pronunciation ended на последнем слышимом окне сгенерированного PCM; это время выхода модели, а не завершения воспроизведения устройством. Демо не показывает исторические блоки фазовых замеров; подробные метрики нативного декодирования, провайдера, кэша и нагрузки микрофона остаются в JSON voicechat-bench. Режим может раскрыть аргументы инструментов, поэтому не используйте его в общих журналах. Таймер фазы сбрасывается между нативным декодированием и ожиданием провайдера.

Интерактивные сеансы сохраняют неизменяемый 37-кадровый голосовой prompt и 20 секунд недавней истории EAR-TTS; Nemotron-H отдельно хранит смысловой контекст разговора. PAD внутри акустического хвоста, зависящего от содержания, генерируются нормально; только последующие беззвучные PAD-кадры каузально обрабатываются пакетами по восемь. Поэтому устный ответ не обрывается, а стоимость attention TTS и работа в простое остаются ограниченными. --live-speech-context-seconds 0 возвращает полную историю TTS, а файловый режим по умолчанию остаётся точным и полным.

В 63,6-секундном профиле на M5 Pro с безопасным акустическим хвостом live-путь показал общий RTF 0,87, RTF 0,71 в последнем окне, синтез 4,1 мс/кадр в последнем окне, пиковый RSS 8,72 ГБ и пиковый физический объём 23,67 ГБ. Активные речевые окна с восемью шагами достигали RTF 1,05 и 1,12, поэтому при накоплении входа интерактивный CLI по-прежнему обратимо переключается на два шага и в аварийном режиме на один. Минимальное косинусное сходство состояний idle-кэша при последовательной и пакетной обработке составило 0,9999999.

Детектор начального щелчка voicechat-bench анализирует вступление и первые 50 мс устойчивой речи. Он отмечает только скачки выше амплитуды 0,05 и шестикратной p99-базы устойчивой речи; сценарий может задать maximum_suspect_onset_transients равным нулю.

Измерено с живыми субтитрами

Три Release-запуска на M5 Pro 48 ГБ показали RTF 0,92, p95 полного кадра 74,8–75,8 мс, первый воспроизводимый звук через 74,2–74,9 мс и около 8,74 ГБ пикового RSS. Транскрипция и ответ совпали во всех запусках.

Архитектура и использование PersonaPlex

PersonaPlex — это многопоточная авторегрессивная модель с тремя ключевыми компонентами:

КомпонентДетали
Temporal Transformer32 слоя, dim=4096, 32 head, SwiGLU (hidden_scale=4.125), RoPE, 8-bit-квантизация (по умолчанию)
Depformer6 слоёв, dim=1024, 16 head, MultiLinear (weights_per_step=true), dep_q=16
Mimi Codec16 codebook, частота кадров 12.5 Гц, аудио-выход 24 кГц

Модель обрабатывает 17 потоков одновременно: 1 текстовый поток + 8 пользовательских аудиопотоков + 8 аудиопотоков агента. Эта архитектура позволяет полнодуплексный диалог, где модель может одновременно слушать и говорить.

Голосовые пресеты

PersonaPlex включает 18 встроенных голосовых пресетов в натуральном и разнообразных стилях:

КатегорияПресеты
Natural FemaleNATF0, NATF1, NATF2, NATF3
Natural MaleNATM0, NATM1, NATM2, NATM3
Varied FemaleVARF0, VARF1, VARF2, VARF3, VARF4
Varied MaleVARM0, 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 = 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, так что PersonaPlex, OmnilingualASR и любые будущие модели на SentencePiece декодируют через одну реализацию токенизатора. См. справочник SentencePieceModel.

Системные промпты

Передайте любой пользовательский системный промпт как обычную строку — внешняя токенизация не нужна:

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

Или используйте встроенный пресет:

Использование 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Пресет системного промпта: assistant, focused, customer-service, teacher
--system-prompt-textПользовательский текст системного промпта (переопределяет --system-prompt)
--max-stepsМаксимум шагов генерации
--streamВыдавать аудио-чанки во время генерации
--compileИспользовать compiled-инференс MLX для более быстрой генерации
--transcriptВыводить текстовый транскрипт вместе с аудио
--jsonJSON-вывод с метаданными

Параметры сэмплирования также можно переопределить:

ОпцияПо умолчаниюОписание
--audio-temp0.8Температура сэмплирования audio-токенов
--audio-top-k250Top-k сэмплирование audio-токенов
--text-temp0.7Температура сэмплирования текстовых токенов
--text-top-k25Top-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
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"))