Mô hình speech-to-speech
Soniqo hỗ trợ hai họ mô hình MLX native cho hội thoại speech-to-speech: PersonaPlex 7B và VoiceChat 11B. Cả hai đều nhận giọng nói và tạo trực tiếp audio của mô hình, nhưng dùng kiến trúc duplex và hợp đồng runtime khác nhau.
Chọn mô hình
| Mô hình | Kiểu hội thoại | Phù hợp nhất cho |
|---|---|---|
| PersonaPlex 7B | Full duplex đồng thời với Moshi, Depformer và Mimi | Nghe/nói chồng lấp và 18 giọng có thể chọn |
| VoiceChat 11B | Duplex liên tục, đồng bộ theo frame với FastConformer, Nemotron-H, EAR-TTS và neural codec | Frame streaming 80 ms, event văn bản/chức năng và giọng native của checkpoint |
Đây là các mô hình speech-to-speech cho hội thoại. Hibiki cũng chuyển trực tiếp giọng nói thành giọng nói, nhưng nhiệm vụ của nó là dịch giọng nói streaming nên được tài liệu hóa riêng.
PersonaPlex 7B
Mô hình hội thoại speech-to-speech song công dựa trên kiến trúc Moshi (Kyutai). PersonaPlex 7B tạo phản hồi nói trực tiếp từ đầu vào nói — không cần pipeline văn bản trung gian. Mô hình đi kèm 18 preset giọng nói và có sẵn ở dạng lượng tử hóa 8-bit (khuyến nghị) và 4-bit. 8-bit là mặc định — nó nhanh hơn 30% và tạo ra phản hồi mạch lạc, trong khi 4-bit làm suy giảm chất lượng đầu ra.
VoiceChat 11B
Tài liệu speech-to-speech duplex: SALM-Duplex: Efficient and Direct Duplex Modeling for Speech-to-Speech Language Model (Interspeech 2025), được model card VoiceChat upstream của NVIDIA trích dẫn.
M5 Pro, 48 GB, Release, nhịp live 80 ms, INT5 với head được bảo vệ: ba lần chạy có kiểm soát đo RTF toàn pipeline là 0.94, 0.92 và 0.92, với RSS đỉnh khoảng 8.70 GB. Văn bản nói đầu tiên xuất hiện sau 44.3–46.7 ms và âm thanh phát được đầu tiên sau 74.0–76.4 ms. Đây là số liệu tính toán đã warm-up, không phải độ trễ luân phiên lượt nói đã học; hiệu năng INT8 sau tối ưu chưa được đo lại.
VoiceChat là runtime speech-to-speech MLX native còn lại trong speech-swift. Nó kết hợp nhận thức FastConformer nhân quả, kênh văn bản/chức năng duplex Nemotron-H, EAR-TTS độc lập điều kiện theo văn bản và neural codec 22,05 kHz. Session liên tục nhận audio mono 16 kHz và trả về sự kiện văn bản cùng một frame đầu ra 1.764 mẫu mỗi 80 ms.
Tải bằng VoiceChatModel.load(from:), bắt đầu VoiceChatSession và đưa audio qua pushAudio. Bundle đầy đủ phải có encoder/, llm/ và tts/; bundle cũ chỉ có phần hiểu sẽ bị từ chối.
INT5 duy trì thông lượng tổng thể dưới RTF 1 trên M5 Pro đã thử nghiệm. Cả ba lần chạy cũng giữ p95 tổng mỗi frame dưới 80 ms, ở mức 76.2–78.6 ms; vì vậy dư địa deadline vẫn hẹp. Hãy đo riêng thời điểm model mở lượt và độ trễ tính toán phần cứng.
Kiến trúc và cách dùng PersonaPlex
PersonaPlex là một mô hình tự hồi quy đa luồng với ba thành phần cốt lõi:
| Thành phần | Chi tiết |
|---|---|
| Temporal Transformer | 32 lớp, dim=4096, 32 head, SwiGLU (hidden_scale=4.125), RoPE, lượng tử hóa 8-bit (mặc định) |
| Depformer | 6 lớp, dim=1024, 16 head, MultiLinear (weights_per_step=true), dep_q=16 |
| Mimi Codec | 16 codebook, tần số khung 12,5 Hz, đầu ra âm thanh 24 kHz |
Mô hình xử lý đồng thời 17 luồng: 1 luồng văn bản + 8 luồng âm thanh người dùng + 8 luồng âm thanh tác nhân. Kiến trúc này cho phép hội thoại song công, mô hình có thể nghe và nói cùng lúc.
Preset giọng nói
PersonaPlex bao gồm 18 preset giọng tích hợp sẵn với các phong cách tự nhiên và đa dạng:
| Danh mục | Preset |
|---|---|
| Giọng nữ tự nhiên | NATF0, NATF1, NATF2, NATF3 |
| Giọng nam tự nhiên | NATM0, NATM1, NATM2, NATM3 |
| Giọng nữ đa dạng | VARF0, VARF1, VARF2, VARF3, VARF4 |
| Giọng nam đa dạng | VARM0, VARM1, VARM2, VARM3, VARM4 |
Độc thoại nội tâm
PersonaPlex tạo hai luồng song song ở mỗi bước: 8 token audio codebook cho codec Mimi và một token văn bản cho độc thoại nội tâm của mô hình. Luồng văn bản là điều mô hình đang “suy nghĩ” khi nói — nó có thể lệch nhẹ so với âm thanh cuối cùng, nhưng trên thực tế nó phản chiếu phản hồi nói đủ sát để dùng như một transcript trực tiếp.
Các token văn bản quay về dưới dạng ID piece SentencePiece thô. Giải mã chúng bằng SentencePieceDecoder mà PersonaPlex cung cấp:
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
Ở chế độ streaming, respondStream phát ra các chunk textTokens khi chúng được tạo ra — giải mã chúng tăng dần để điều khiển một view phụ đề trực tiếp trong khi âm thanh vẫn đang được tạo. Cờ CLI --transcript thực hiện chính xác điều này phía sau hậu trường.
Tại sao điều đó quan trọng: SentencePieceDecoder được xây dựng trên bộ đọc protobuf chia sẻ AudioCommon.SentencePieceModel, vì vậy PersonaPlex, OmnilingualASR và mọi mô hình dựa trên SentencePiece trong tương lai đều giải mã qua cùng một triển khai tokenizer. Xem tham chiếu SentencePieceModel.
System prompt
Truyền bất kỳ system prompt tùy chỉnh nào dưới dạng chuỗi thuần — không cần tokenize bên ngoài:
let response = model.respond(
userAudio: audio,
voice: .NATM0,
systemPrompt: "You enjoy having a good conversation."
)
Hoặc dùng preset có sẵn:
assistant— Trợ lý hữu ích mục đích chung (mặc định)focused— Phản hồi ngắn gọn, trực tiếpcustomer-service— Tác nhân hỗ trợ lịch sự, hướng giải phápteacher— Phong cách giảng dạy kiên nhẫn, giải thích
Sử dụng CLI
Tạo phản hồi nói từ đầu vào âm thanh:
# 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
Tùy chọn
| Tùy chọn | Mô tả |
|---|---|
--input | Tệp âm thanh đầu vào (WAV, bắt buộc) |
--voice | Tên preset giọng (ví dụ NATM0, VARF2) |
--system-prompt | Preset system prompt: assistant, focused, customer-service, teacher |
--system-prompt-text | Văn bản system prompt tùy chỉnh (ghi đè --system-prompt) |
--max-steps | Số bước sinh tối đa |
--stream | Phát các chunk âm thanh trong khi sinh |
--compile | Dùng suy luận compile của MLX để sinh nhanh hơn |
--transcript | Đầu ra transcript văn bản kèm âm thanh |
--json | Đầu ra JSON với metadata |
Các tham số lấy mẫu cũng có thể được ghi đè:
| Tùy chọn | Mặc định | Mô tả |
|---|---|---|
--audio-temp | 0.8 | Nhiệt độ lấy mẫu token âm thanh |
--audio-top-k | 250 | Lấy mẫu top-k token âm thanh |
--text-temp | 0.7 | Nhiệt độ lấy mẫu token văn bản |
--text-top-k | 25 | Lấy mẫu top-k token văn bản |
Streaming
Cờ --stream bật đầu ra âm thanh thời gian thực. Các chunk âm thanh được phát ra khi chúng được tạo, vì vậy việc phát lại có thể bắt đầu trước khi phản hồi đầy đủ hoàn tất. Điều này đặc biệt hữu ích cho các ứng dụng tương tác mà độ trễ thấp là quan trọng.
Hiệu năng
| Chỉ số | Giá trị |
|---|---|
| Hệ số thời gian thực (RTF) | ~1,4 (8-bit, gần thời gian thực) |
| Độ trễ mỗi bước | ~112 ms/bước trên M2 Max (8-bit) |
| Kích thước mô hình (8-bit) | ~9,1 GB |
| RAM đỉnh (8-bit) | ~11 GB |
| Kích thước mô hình (4-bit) | ~4,9 GB |
| RAM đỉnh (4-bit) | ~7 GB |
PersonaPlex 7B (8-bit) yêu cầu tối thiểu 24 GB RAM. Biến thể 4-bit chạy được trên thiết bị 16 GB nhưng tạo ra đầu ra suy giảm. Trên thiết bị 8 GB, không biến thể nào chạy được. Dùng --compile để có hiệu năng tốt nhất trên phần cứng hỗ trợ.
Biến thể mô hình
| Mô hình | Kích thước | HuggingFace |
|---|---|---|
| PersonaPlex-7B (8-bit) khuyến nghị | 9,1 GB | aufklarer/PersonaPlex-7B-MLX-8bit |
| PersonaPlex-7B (4-bit) | 4,9 GB | aufklarer/PersonaPlex-7B-MLX-4bit |
API Swift
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"))