⚠ 번역이 오래되었습니다

VoiceChat 도구 호출 및 성능에 대한 최신 내용은 영어 문서를 참조하세요.

음성-음성 모델

Soniqo는 대화형 음성-음성을 위한 두 가지 네이티브 MLX 모델 계열, PersonaPlex 7BVoiceChat 11B를 지원합니다. 둘 다 음성을 받아 모델 오디오를 직접 생성하지만, 듀플렉스 아키텍처와 런타임 계약은 다릅니다.

모델 선택

모델대화 방식적합한 용도
PersonaPlex 7BMoshi, Depformer, Mimi를 사용하는 동시 전이중듣기와 말하기의 중첩, 선택 가능한 18개 음성
VoiceChat 11BFastConformer, Nemotron-H, EAR-TTS, 신경 코덱을 사용하는 연속 프레임 동기식 듀플렉스80 ms 스트리밍 프레임, 텍스트/함수 이벤트, 체크포인트 고유 음성

이 페이지의 모델은 대화형 음성-음성 모델입니다. Hibiki도 음성을 직접 음성으로 변환하지만, 스트리밍 음성 번역 모델이므로 별도로 설명합니다.

PersonaPlex 7B

Moshi 아키텍처(Kyutai) 기반의 전이중 음성-음성 대화 모델입니다. PersonaPlex 7B는 음성 입력에서 직접 음성 응답을 생성하며, 중간 텍스트 파이프라인이 필요하지 않습니다. 모델은 18개의 음색 프리셋과 함께 제공되며 8비트(권장)와 4비트 양자화로 이용할 수 있습니다. 8비트가 기본입니다 — 30% 더 빠르고 일관된 응답을 생성하는 반면 4비트는 출력 품질이 저하됩니다.

VoiceChat 11B

전이중 speech-to-speech 참고: 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: 제어된 3회 실행의 전체 파이프라인 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 미만의 평균 처리량을 유지합니다. 3회 모두 프레임 전체 p95도 76.2–78.6 ms로 80 ms 미만이었지만 마감 여유는 아직 좁습니다. 모델 발화 시작과 하드웨어 계산 지연은 별도로 측정해야 합니다.

라이브 VoiceChat CLI

NVIDIA Nemotron VoiceChat 11B로 구동되는 완전한 protected-head INT5 bundle을 사용해 Soniqo와 실시간 대화합니다. Apple AEC를 위해 캡처와 재생을 하나의 AVAudioEngine에 두고, bundle의 RNN-T 사용자 자막을 스트리밍하며, 계속 듣는 동안 22.05 kHz 모델 오디오를 재생합니다.

기본 Soniqo prompt는 기능 범위를 명확히 제한합니다. 이 CLI는 대화하고 질문에 답할 수 있지만 캘린더, 미리 알림, 앱, 계정, 기기 또는 외부 서비스에 접근할 수 없습니다. 이런 요청에는 작업을 수행할 수 없다고 분명히 말하고, 이행할 수 없는 확인을 요구하지 않으며, 사용자가 직접 할 수 있는 방법을 간단히 안내합니다.

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만 공개합니다. 목록 탐색은 어댑터 내부에서 비공개로 수행됩니다. 업데이트는 사용자가 말한 미리 알림 이름을 런타임에서 신뢰할 수 있는 비공개 EventKit ID로 해석하며, 이 ID는 모델에 보이는 도구 결과에 포함되지 않습니다. 생성과 기한은 계속 한 번의 EventKit 저장으로 커밋됩니다. 첫 서버 시작은 최소 60초, 일반 도구 호출은 기본 15초를 사용합니다.

MCP는 --mcp-config를 지정할 때만 활성화됩니다. 범용 JSON 설정으로 어떤 stdio MCP server든 시작하고 enabledTools에서 최대 5개 도구를 명시적으로 선택합니다. readOnlyTools에 없는 도구는 모두 쓰기로 취급됩니다. 쓰기 기본 정책은 confirm이며 첫 호출을 보류합니다. Soniqo의 결정론적 확인 음성이 해석된 인수를 반복하고 아직 아무것도 바뀌지 않았음을 알립니다. 사용자의 새로운 긍정 응답이 있을 때만 한 번 실행합니다. 새로운 사용자 발화 없이는 동일한 호출을 두 번 실행할 수 없으며 allow에서도 같습니다. deny 또는 명시적 세션 승인인 allow도 사용할 수 있습니다. 실제 결과는 모델의 함수 채널로 돌아오며 성공에는 ok 응답이 필요합니다.

쓰기 인수는 확인 전에 사용자 전사와 대조됩니다. 상대 날짜는 현재 현지 날짜를 기준으로 계산하며, 지어낸 목록 이름, 지난 날짜, 우선순위 등 확인되지 않은 값은 실행하지 않습니다. 새로운 RNN-T 발화와 발화 종료로 전사가 재설정된 뒤에도 확인 응답을 처리합니다. Soniqo는 MCP server가 실제로 ok: true를 반환한 경우에만 성공을 말하며, 그렇지 않으면 실행을 확인할 수 없었다고 알립니다.

“어떤 미리 알림이 있나요?”는 모든 EventKit 목록을 가로지르는 단 한 번의 평탄화된 list_reminders 호출을 사용합니다. 목록, 기한, 완료 상태와 런타임 전용 안정 ID를 캐시하므로 세부 질문과 안전한 업데이트를 위해 목록별로 다시 읽지 않습니다. 대시보드는 함수 디코딩의 경과 시간, token step, token/s를 오디오 RTF와 별도로 표시하며, RTF는 계속 전경의 80 ms 오디오 프레임만 측정합니다.

음성 확인은 사용자가 “계속 진행”할지 묻고 긍정 허용 목록의 표현을 의도적으로 사용하지 않으므로, 재생 에코가 대기 중인 작업을 승인할 수 없습니다.

하위 MCP server는 PATH, HOME, 임시 디렉터리와 로캘 변수만 상속합니다. 자격 증명은 server의 env map에 명시적으로 전달해야 하며, 상위 process의 관련 없는 secret은 전달되지 않습니다.

8-bit 함수 프로젝션은 약 518 MB입니다. 일반 발화 중 VoiceChat은 캐시된 36 KB PAD/도구 시작 프로브만 평가합니다. 131,072행 전체 헤드는 도구 시작이 먼저 PAD를 앞설 때만 실행되고 전역 argmax를 확인한 뒤, 열린 JSON 호출을 생성하는 동안 계속 활성화됩니다. MCP 결과가 확정되면 VoiceChat은 제한된 16-token causal-prefill 청크로 이를 재생하여 학습된 함수 피드백과 language/speech cache 상태를 보존하면서 결과 token마다 11B decode를 수행하지 않습니다. 따라서 도구 시작 판단을 약화하지 않으면서 MCP 대화를 실시간으로 유지합니다.

재생 전 기본으로 80 ms 프레임 3개(240 ms)를 버퍼링합니다. 스피커 끊김 후에는 8프레임 복구 버퍼를 기다립니다. 첫 88 ms 콜백 또는 대기 프레임 3개에서 음성 정제를 8회에서 2회로 낮추며, 120 ms, 6개 프레임 또는 입력 재동기화에서는 즉시 1회로 낮춥니다. 그래도 기본 8프레임/640 ms 한도에 도달하면 새 입력을 받는 데 필요한 최소한의 오래된 마이크 오디오만 버립니다. 지속 과부하는 하나의 복구 구간으로 처리하며 RNN-T가 이미 확인한 사용자 턴은 반복 초기화로 지워지지 않습니다. 건너뛴 단어는 계속 표시되며 다시 말해야 할 수 있습니다.

대시보드의 behind는 대기 중인 마이크 오디오, RTF 0.93×는 계산 시간÷오디오 시간(1 미만은 따라잡고 1 초과는 뒤처짐), last 74 ms for 80 ms audio는 고정 80 ms 모델 프레임 계산에 74 ms가 걸렸다는 뜻입니다. 스피커 끊김, 오래된 오디오 건너뛰기, 건너뛴 마이크 오디오도 명확히 표시합니다. 이동 평균은 최근 마이크 콜백 120개를 사용하며 재생 이벤트를 추가 오디오 시간으로 세지 않습니다.

마이크 모드는 기본으로 NVIDIA 방식 RNN-T 턴테이킹을 사용합니다. 모델이 학습한 BOS/EOS 판단이 일반적인 저지연 경로로 유지됩니다. 각 80 ms 프레임의 첫 예측을 blank 또는 non-blank로 처리하며, 사용자 음성이 확인된 뒤에는 연속 blank 40프레임(3.2초)이 응답 시작을 위한 안전 폴백으로만 동작하고 새로운 non-blank 40프레임이 대응하는 끼어들기 폴백을 제공합니다. Soniqo가 말하기 시작할 때 음성 카운터가 초기화되므로 완료된 사용자 턴의 활동이 응답을 즉시 끊지 않습니다. 모든 오디오 프레임은 계속 duplex 모델에 전달됩니다. 이 폴백을 끄고 BOS/EOS 결정을 학습된 언어 헤드에만 맡기려면 --no-rnnt-turn-taking을 사용하세요. 대시보드는 RNN-T barge-in과 스피커 끊김을 별도로 표시합니다.

일반 모드에서는 사용자 음성이 확인될 때까지 모델 고유 BOS를 억제하므로 Soniqo가 먼저 말하지 않습니다. --greet를 지정해야만 첫 인사를 명시적으로 허용합니다. 들리는 응답 내용 뒤의 blank PAD는 최소 16프레임 또는 내용 token당 3프레임 중 더 긴 예산까지 열린 턴 안에서 처리됩니다. 예산을 넘는 첫 blank PAD가 논리적 턴을 닫습니다. 이 내용 비례 꼬리는 고정 1.28초 제한 때문에 늦게 생성되는 단어가 음소거되는 일을 막습니다.

대화형 모드는 터미널의 대체 화면에서 고정 대시보드를 사용하므로 상태 업데이트가 스크롤백을 채우지 않고 제자리에서 다시 그려집니다. 추가 전용 출력에는 --plain을 사용하세요.

로컬 진단에서는 --debug-timeline이 사용자와 Soniqo의 각 발화에 시간을 표시하고, 파싱된 네이티브 호출 인수, MCP 시작/완료, 결과 캐시 동기화를 같은 시간순으로 삽입합니다. 마지막으로 들리는 생성 PCM 창에 pronunciation ended도 표시합니다. 이는 모델 출력 시간이며 스피커 재생 완료 시간이 아닙니다. 데모는 과거 단계 타이밍 블록을 의도적으로 생략하며 네이티브 디코딩, 제공자, 캐시 및 마이크 압력의 상세 수치는 voicechat-bench JSON에 남습니다. 도구 인수가 노출될 수 있으므로 공유 로그에서는 사용하지 마세요. 단계 타이머는 네이티브 디코딩과 제공자 대기 사이에서 초기화됩니다.

라이브 세션은 변경되지 않는 37프레임 화자 프롬프트와 최근 20초 EAR-TTS 기록을 유지하고, 의미 기반 대화 기록은 Nemotron-H가 별도로 보존합니다. 내용 비례 음성 꼬리 안의 PAD는 정상적으로 생성하고, 그 뒤의 무음 PAD만 8개씩 인과적으로 진행합니다. 따라서 말한 응답을 자르지 않으면서 TTS 어텐션 비용과 유휴 연산이 무한히 늘지 않습니다. --live-speech-context-seconds 0은 전체 TTS 기록을 복원하며 파일 모드는 기본적으로 정확한 전체 기록을 사용합니다.

M5 Pro의 63.6초 프로파일에서 안전한 음성 꼬리를 사용한 라이브 경로는 전체 RTF 0.87, 마지막 구간 RTF 0.71, 마지막 구간 합성 4.1 ms/프레임, 최대 RSS 8.72 GB, 최대 물리 메모리 사용량 23.67 GB를 기록했습니다. 8단계로 음성을 생성 중인 구간은 RTF 1.05와 1.12에 도달했으므로 입력이 쌓이면 대화형 CLI는 계속 가역적으로 2단계로 전환하고 비상 시 1단계를 사용합니다. 순차 및 배치 유휴 캐시 상태의 최소 코사인 유사도는 0.9999999였습니다.

voicechat-bench의 시작 팝 감지기는 생성 오디오의 리드인과 지속 음성의 첫 50 ms를 검사합니다. 샘플 점프가 0.05 진폭과 안정 음성 p99 기준의 6배를 모두 넘을 때만 표시하며, 시나리오에서 maximum_suspect_onset_transients를 0으로 설정해 회귀를 막을 수 있습니다.

라이브 자막 활성화 측정

M5 Pro 48 GB Release에서 세 번 실행한 결과 RTF는 모두 0.92, 전체 프레임 p95는 74.8–75.8 ms, 첫 재생 가능 오디오는 74.2–74.9 ms, peak RSS는 약 8.74 GB였습니다. 자막과 응답은 세 실행에서 동일했습니다.

PersonaPlex 아키텍처와 사용법

PersonaPlex는 세 가지 핵심 구성 요소를 가진 멀티 스트림 자기회귀 모델입니다:

구성 요소세부 내용
Temporal Transformer32 레이어, dim=4096, 32 헤드, SwiGLU (hidden_scale=4.125), RoPE, 8비트 양자화 (기본)
Depformer6 레이어, dim=1024, 16 헤드, MultiLinear (weights_per_step=true), dep_q=16
Mimi Codec16 코드북, 12.5 Hz 프레임 속도, 24 kHz 오디오 출력

모델은 동시에 17개 스트림을 처리합니다: 텍스트 스트림 1개 + 사용자 오디오 스트림 8개 + agent 오디오 스트림 8개. 이 아키텍처는 모델이 듣고 말하는 것을 동시에 할 수 있는 전이중 대화를 가능하게 합니다.

음색 프리셋

PersonaPlex는 자연스러운 스타일과 다양한 스타일에 걸쳐 18개의 내장 음색 프리셋을 포함합니다:

카테고리프리셋
자연스러운 여성NATF0, NATF1, NATF2, NATF3
자연스러운 남성NATM0, NATM1, NATM2, NATM3
다양한 여성VARF0, VARF1, VARF2, VARF3, VARF4
다양한 남성VARM0, VARM1, VARM2, VARM3, VARM4

내부 독백

PersonaPlex는 매 스텝마다 두 개의 병렬 스트림을 생성합니다: Mimi 코덱용 8개 오디오 코드북 토큰과 모델의 내부 독백을 위한 텍스트 토큰 1개. 텍스트 스트림은 모델이 말하면서 “생각”하는 것입니다 — 최종 오디오와 약간 다를 수 있지만, 실제로는 음성 응답과 충분히 가까워서 라이브 전사로 사용할 수 있습니다.

텍스트 토큰은 raw 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 청크를 방출합니다 — 오디오가 아직 생성 중인 동안 이를 점진적으로 디코딩하여 라이브 캡션 뷰를 구동합니다. --transcript CLI 플래그는 내부적으로 이 동작을 수행합니다.

왜 중요한가: 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더 빠른 생성을 위해 MLX 컴파일된 추론 사용
--transcript오디오와 함께 텍스트 전사 출력
--json메타데이터를 포함한 JSON 출력

샘플링 파라미터도 재정의할 수 있습니다:

옵션기본값설명
--audio-temp0.8오디오 토큰 샘플링 temperature
--audio-top-k250오디오 토큰 top-k 샘플링
--text-temp0.7텍스트 토큰 샘플링 temperature
--text-top-k25텍스트 토큰 top-k 샘플링

스트리밍

--stream 플래그는 실시간 오디오 출력을 활성화합니다. 오디오 청크가 생성되는 대로 방출되므로 전체 응답이 완성되기 전에 재생을 시작할 수 있습니다. 이는 지연이 중요한 인터랙티브 애플리케이션에 특히 유용합니다.

성능

지표
실시간 계수 (RTF)약 1.4 (8비트, 실시간에 근접)
스텝 지연M2 Max에서 약 112 ms/step (8비트)
모델 크기 (8비트)약 9.1 GB
피크 RAM (8비트)약 11 GB
모델 크기 (4비트)약 4.9 GB
피크 RAM (4비트)약 7 GB
중요

PersonaPlex 7B (8비트)는 최소 24 GB의 RAM이 필요합니다. 4비트 변형은 16 GB 기기에 맞지만 저하된 출력을 생성합니다. 8 GB 기기에서는 두 변형 모두 맞지 않습니다. 지원되는 하드웨어에서 최상의 성능을 위해 --compile을 사용하세요.

모델 변형

모델크기HuggingFace
PersonaPlex-7B (8비트) 권장9.1 GBaufklarer/PersonaPlex-7B-MLX-8bit
PersonaPlex-7B (4비트)4.9 GBaufklarer/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"))