โมเดล Speech-to-Speech
Soniqo รองรับโมเดล native MLX สำหรับการสนทนา speech-to-speech สองตระกูล ได้แก่ PersonaPlex 7B และ VoiceChat 11B ทั้งสองรับเสียงพูดและสร้างเสียงของโมเดลโดยตรง แต่ใช้สถาปัตยกรรม duplex และสัญญา runtime ต่างกัน
เลือกโมเดล
| โมเดล | รูปแบบการสนทนา | เหมาะที่สุดสำหรับ |
|---|---|---|
| PersonaPlex 7B | Full duplex พร้อมกันด้วย Moshi, Depformer และ Mimi | การฟังและพูดที่ซ้อนกัน พร้อมเสียงให้เลือก 18 แบบ |
| VoiceChat 11B | Duplex ต่อเนื่องแบบ frame-synchronous ด้วย FastConformer, Nemotron-H, EAR-TTS และ neural codec | Streaming frame 80 ms, event ข้อความ/ฟังก์ชัน และเสียง native ของ checkpoint |
โมเดลเหล่านี้เป็น speech-to-speech สำหรับการสนทนา Hibiki ก็แปลงเสียงพูดเป็นเสียงพูดโดยตรง แต่มีหน้าที่แปลเสียงพูดแบบ streaming จึงแยกเอกสารไว้ต่างหาก
PersonaPlex 7B
โมเดล speech-to-speech บทสนทนาแบบฟูล-ดูเพล็กซ์ ที่ใช้สถาปัตยกรรม Moshi (Kyutai) PersonaPlex 7B สร้างคำตอบที่พูดได้โดยตรงจากอินพุตที่เป็นเสียงพูด — ไม่จำเป็นต้องมี pipeline ข้อความตรงกลาง โมเดลมาพร้อมพรีเซ็ตเสียง 18 ตัวและพร้อมใช้ในรูปแบบที่ลดความละเอียดเป็น 8-bit (แนะนำ) และ 4-bit 8-bit เป็นค่าเริ่มต้น — เร็วกว่า 30% และสร้างคำตอบที่สอดคล้องกัน ในขณะที่ 4-bit จะลดคุณภาพเอาต์พุต
VoiceChat 11B
เอกสารอ้างอิง duplex speech-to-speech: SALM-Duplex: Efficient and Direct Duplex Modeling for Speech-to-Speech Language Model (Interspeech 2025) ซึ่ง model card VoiceChat ต้นทางของ NVIDIA อ้างอิง
M5 Pro, 48 GB, Release, จังหวะ live 80 ms, INT5 แบบ protected-head: การรันแบบควบคุมสามครั้งวัด whole-pipeline RTF ได้ 0.94, 0.92 และ 0.92 โดยมี RSS สูงสุดประมาณ 8.70 GB ข้อความพูดแรกมาใน 44.3–46.7 ms และเสียงแรกที่เล่นได้มาใน 74.0–76.4 ms ตัวเลขเหล่านี้คือเวลา compute หลัง warm-up ไม่ใช่ latency การผลัดกันพูดที่เรียนรู้ และยังไม่ได้วัด performance ของ INT8 หลัง optimize ซ้ำ
VoiceChat คือ runtime เสียงพูดสู่เสียงพูดแบบ native MLX อีกตัวใน speech-swift โดยรวมการรับรู้ FastConformer แบบ causal, ช่องข้อความ/ฟังก์ชัน duplex ของ Nemotron-H, EAR-TTS แบบกำหนดเงื่อนไขด้วยข้อความที่แยกอิสระ และ neural codec 22.05 kHz Session รับเสียง mono 16 kHz อย่างต่อเนื่อง และส่ง text event พร้อม output frame 1,764 samples ทุก 80 ms
โหลดด้วย VoiceChatModel.load(from:) เริ่ม VoiceChatSession และป้อนเสียงผ่าน pushAudio Bundle แบบเต็มต้องมี encoder/, llm/ และ tts/; bundle รุ่นเก่าที่มีเฉพาะส่วนความเข้าใจจะถูกปฏิเสธ
INT5 รักษา throughput รวมต่ำกว่า RTF 1 บน M5 Pro ที่ทดสอบ การรันทั้งสามยังรักษา total/frame p95 ต่ำกว่า 80 ms ที่ 76.2–78.6 ms แต่ deadline headroom ยังแคบ ควรวัดจังหวะเริ่มพูดของโมเดลและ latency การคำนวณของฮาร์ดแวร์แยกกัน
สถาปัตยกรรมและการใช้งาน PersonaPlex
PersonaPlex เป็นโมเดล autoregressive แบบหลายสตรีมที่มีส่วนประกอบหลัก 3 ส่วน:
| ส่วนประกอบ | รายละเอียด |
|---|---|
| Temporal Transformer | 32 layers, dim=4096, 32 heads, SwiGLU (hidden_scale=4.125), RoPE, 8-bit quantized (default) |
| Depformer | 6 layers, dim=1024, 16 heads, MultiLinear (weights_per_step=true), dep_q=16 |
| Mimi Codec | 16 codebooks, 12.5 Hz frame rate, 24 kHz audio output |
โมเดลประมวลผล 17 สตรีม พร้อมกัน: 1 สตรีมข้อความ + 8 สตรีมเสียงผู้ใช้ + 8 สตรีมเสียงเอเจนต์ สถาปัตยกรรมนี้รองรับบทสนทนาแบบฟูล-ดูเพล็กซ์ที่โมเดลสามารถฟังและพูดได้ในเวลาเดียวกัน
พรีเซ็ตเสียง
PersonaPlex มาพร้อมพรีเซ็ตเสียง 18 ตัวในสไตล์ที่เป็นธรรมชาติและหลากหลาย:
| หมวดหมู่ | พรีเซ็ต |
|---|---|
| หญิงธรรมชาติ | NATF0, NATF1, NATF2, NATF3 |
| ชายธรรมชาติ | NATM0, NATM1, NATM2, NATM3 |
| หญิงหลากหลาย | VARF0, VARF1, VARF2, VARF3, VARF4 |
| ชายหลากหลาย | VARM0, VARM1, VARM2, VARM3, VARM4 |
ความคิดภายใน (Inner Monologue)
PersonaPlex สร้างสตรีมขนานสองสตรีมในทุกขั้นตอน: 8 codebook token เสียงสำหรับ codec Mimi และ 1 text token สำหรับ inner monologue ของโมเดล สตรีมข้อความคือสิ่งที่โมเดล “กำลังคิด” ขณะพูด — อาจเบี่ยงเบนเล็กน้อยจากเสียงสุดท้าย แต่ในทางปฏิบัติจะสะท้อนคำตอบที่พูดได้ใกล้พอที่จะใช้เป็นบทถอดความสด
Text token จะกลับมาเป็น raw 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
ในโหมด streaming, respondStream จะปล่อย textTokens chunk ทีละชุดเมื่อถูกสร้างขึ้น — ถอดรหัสทีละขั้นเพื่อขับเคลื่อนหน้า caption แบบสดในขณะที่เสียงยังคงสร้างอยู่ แฟล็ก --transcript CLI ทำสิ่งนี้เบื้องหลังพอดี
เหตุใดเรื่องนี้จึงสำคัญ: SentencePieceDecoder สร้างขึ้นบน protobuf reader ของ AudioCommon.SentencePieceModel ที่ใช้ร่วมกัน ดังนั้น PersonaPlex, OmnilingualASR และโมเดลที่ใช้ SentencePiece อนาคตจึงถอดรหัสผ่าน tokenizer แบบเดียวกัน ดู เอกสารอ้างอิง SentencePieceModel
System Prompt
ส่ง system prompt แบบกำหนดเองใด ๆ เป็นสตริงธรรมดา — ไม่จำเป็นต้องใช้ tokenization ภายนอก:
let response = model.respond(
userAudio: audio,
voice: .NATM0,
systemPrompt: "You enjoy having a good conversation."
)
หรือใช้พรีเซ็ตในตัว:
assistant— ผู้ช่วยอเนกประสงค์ที่เป็นประโยชน์ (ค่าเริ่มต้น)focused— คำตอบที่กระชับและตรงไปตรงมาcustomer-service— เจ้าหน้าที่สนับสนุนที่สุภาพและเน้นการแก้ปัญหาteacher— สไตล์การสอนที่อดทนและอธิบายชัดเจน
การใช้งาน 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 | พรีเซ็ต system prompt: assistant, focused, customer-service, teacher |
--system-prompt-text | ข้อความ system prompt แบบกำหนดเอง (เขียนทับ --system-prompt) |
--max-steps | ขั้นตอนการสร้างสูงสุด |
--stream | ปล่อย chunk เสียงระหว่างการสร้าง |
--compile | ใช้ MLX compiled inference เพื่อการสร้างที่เร็วขึ้น |
--transcript | เอาต์พุตบทถอดความข้อความพร้อมกับเสียง |
--json | เอาต์พุต JSON พร้อม metadata |
พารามิเตอร์การสุ่มสามารถเขียนทับได้:
| ตัวเลือก | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|
--audio-temp | 0.8 | อุณหภูมิการสุ่ม audio token |
--audio-top-k | 250 | การสุ่ม top-k ของ audio token |
--text-temp | 0.7 | อุณหภูมิการสุ่ม text token |
--text-top-k | 25 | การสุ่ม top-k ของ text token |
Streaming
แฟล็ก --stream เปิดใช้งานเอาต์พุตเสียงแบบเรียลไทม์ Chunk เสียงจะถูกปล่อยออกมาเมื่อถูกสร้างขึ้น ดังนั้นการเล่นจึงเริ่มต้นได้ก่อนที่คำตอบจะเสร็จสมบูรณ์ มีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันแบบโต้ตอบที่หน่วงต่ำเป็นสิ่งสำคัญ
ประสิทธิภาพ
| เมตริก | ค่า |
|---|---|
| Real-time factor (RTF) | ~1.4 (8-bit, ใกล้เรียลไทม์) |
| หน่วงของขั้นตอน | ~112 ms/step บน M2 Max (8-bit) |
| ขนาดโมเดล (8-bit) | ~9.1 GB |
| RAM สูงสุด (8-bit) | ~11 GB |
| ขนาดโมเดล (4-bit) | ~4.9 GB |
| RAM สูงสุด (4-bit) | ~7 GB |
PersonaPlex 7B (8-bit) ต้องการ RAM อย่างน้อย 24 GB รุ่น 4-bit พอดีบนอุปกรณ์ 16 GB แต่ผลลัพธ์จะเสื่อมคุณภาพ บนอุปกรณ์ 8 GB จะไม่มีรุ่นใดพอดี ใช้ --compile เพื่อประสิทธิภาพที่ดีที่สุดบนฮาร์ดแวร์ที่รองรับ
รุ่นย่อยของโมเดล
| โมเดล | ขนาด | HuggingFace |
|---|---|---|
| PersonaPlex-7B (8-bit) แนะนำ | 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"))