การบอกพิมพ์แบบสตรีมมิ่ง
Parakeet-EOU-120M เป็นโมเดล ASR แบบสตรีมมิ่ง RNN-T ขนาดเล็กพร้อมหัว end-of-utterance (EOU) ที่ชัดเจน สร้างขึ้นสำหรับการบอกพิมพ์เรียลไทม์บน Neural Engine ของ Apple Silicon คู่มือนี้ยังครอบคลุม DictateDemo ซึ่งเป็นแอปตัวอย่างบนเมนูบาร์ macOS ที่ผูกโมเดลสตรีมมิ่งเข้ากับ Silero VAD เพื่อการบอกพิมพ์แบบไม่ต้องใช้มือพร้อมวางผลลัพธ์ได้ทุกที่
คืออะไร
- ผลลัพธ์บางส่วนแบบสด — ข้อความอัปเดตขณะที่คุณพูด ~340 ms หลังจากแต่ละ chunk
- EOU ที่ชัดเจน — โมเดลตัดสินใจเองว่าประโยคจบเมื่อใด ไม่ต้องใช้ปุ่มกด
- Force-finalize ที่ขับด้วย VAD — Silero ทำหน้าที่เป็นแบ็คอัพคอมมิตประโยคแม้ EOU จะติดขัดเพราะเสียงรบกวนพื้นหลัง
- 120 MB INT8 CoreML — รันบน Neural Engine ปล่อย GPU ว่างสำหรับโมเดลอื่น
- 25 ภาษายุโรป — ใช้ตระกูล vocabulary เดียวกับ NeMo Parakeet TDT ต้นทาง
สถาปัตยกรรม
โมเดล CoreML สามตัวต่อกันเป็น pipeline ต่อ chunk เสียง:
| ส่วนประกอบ | คำอธิบาย |
|---|---|
| Encoder | Conformer แบบรับรู้ cache รับ chunk mel 64 frame (640 ms) พร้อม tensor สถานะหกตัว ได้แก่ KV cache ของ attention, cache ของ depthwise conv และ pre_cache mel loopback ที่ใส่เสียงในอดีตล่าสุดไว้ด้านหน้าเพื่อให้ FFT มองเห็นสัญญาณต่อเนื่องข้ามขอบเขตของ chunk |
| Decoder | เครือข่ายทำนาย LSTM แบบขั้นเดียว รับ token non-blank ก่อนหน้าและส่ง embedding พร้อมสถานะ (h, c) ที่อัปเดต |
| Joint + หัว EOU | รวมเอาต์พุตของ encoder กับ decoder เป็น logits ครอบคลุม vocab + blank + EOU คลาส EOU คือสัญญาณชัดของโมเดลว่าประโยคจบแล้ว |
ทำไมต้องมี token EOU แยก
RNNT ล้วน ๆ จะปล่อย blank ในช่วงเงียบ ซึ่ง decoder ดูดซับโดยไม่ส่งสัญญาณว่า "ประโยคจบแล้ว" หัว EOU เฉพาะทำให้โมเดลสามารถตัดสินใจอย่างเด็ดขาดในการ commit ผลลัพธ์บางส่วนให้เป็นผลสุดท้าย รีเซ็ตสถานะการใส่เครื่องหมายวรรคตอน/ตัวพิมพ์ใหญ่ และกระตุ้นการกระทำต่อ ๆ ไป เช่น paste เข้าแอป
เสียงคลิกคีย์บอร์ด การเคลื่อนเมาส์ และเสียงในห้องระหว่างช่วง "เงียบ" อาจทำให้ joint ปล่อย token non-blank บางครั้ง รีเซ็ตตัวจับเวลา debounce ของ EOU และทำให้ commit ติดขัด pipeline ระดับ production จึงจับคู่ EOU ของ joint เข้ากับแบ็คอัพ forceEndOfUtterance() ภายนอกที่ขับด้วย VAD ดูที่ DictateDemo ด้านล่าง
โมเดล
| โมเดล | ขนาด | HuggingFace |
|---|---|---|
| Parakeet-EOU-120M (CoreML INT8) | ~120 MB | aufklarer/Parakeet-EOU-120M-CoreML-INT8 |
ประสิทธิภาพ
| ตัวชี้วัด | ค่า |
|---|---|
| หน่วยความจำของน้ำหนัก | ~120 MB (INT8) |
| หน่วยความจำการอนุมานสูงสุด | ~200 MB |
| Chunk latency (ชิป M-series) | ~30 ms คำนวณ / 640 ms ของเสียง (RTF ~0.056) |
| Partial latency end-to-end | ~340 ms (หนึ่ง chunk) |
| Commit latency (เส้นทาง VAD) | ~1 s หลังหยุดพูด |
| เป้าหมายการประมวลผล | Neural Engine (CoreML) |
Quick start — การถอดเสียงเป็นชุด
โมเดลสตรีมมิ่งยังเข้ากันกับ SpeechRecognitionModel จึงใช้แทนกันได้กับโค้ดใด ๆ ที่รับโมเดล STT ทั่วไป:
import ParakeetStreamingASR
let model = try await ParakeetStreamingASRModel.fromPretrained()
let text = try model.transcribeAudio(audioSamples, sampleRate: 16000)
Quick start — สตรีมมิ่งแบบ async
for await partial in model.transcribeStream(audio: samples, sampleRate: 16000) {
if partial.isFinal { print("FINAL: \(partial.text)") }
else { print("... \(partial.text)") }
}
PartialTranscript แต่ละชิ้นมี text, isFinal, confidence, eouDetected (joint ยิงเองหรือถูก force-finalize) และ segmentIndex ที่เพิ่มขึ้นเป็นลำดับ
API session แบบยาว (อินพุตจากไมโครโฟน)
สำหรับการบอกพิมพ์สด ให้สร้าง session ครั้งเดียวแล้วป้อน chunk เข้าไปเมื่อมาจากไมโครโฟน Session จะเก็บบัฟเฟอร์ภายในและรัน encoder เมื่อสะสมตัวอย่างพอ คุณจึงป้อน chunk ขนาดใดก็ได้:
let session = try model.createSession()
// แต่ละ chunk จากไมค์:
let partials = try session.pushAudio(float32Chunk16kHz)
for p in partials {
if p.isFinal { commit(p.text) }
else { showPartial(p.text) }
}
// เมื่อสตรีมจบ:
let trailing = try session.finalize()
แพทเทิร์น force-finalize ด้วย VAD
เมื่อมี Silero VAD รันอยู่ใน pipeline ของคุณแล้ว ให้ใช้มันขับ commit สำรองเพื่อไม่ให้เสียงรบกวนพื้นหลังทำให้ตัวจับเวลา debounce ของ EOU ติดขัด:
if hasPendingUtterance && !vadSpeechActive && vadSilentChunks >= 30 {
// ~960 ms ของความเงียบต่อเนื่องตาม Silero
if let forced = session.forceEndOfUtterance() {
commit(forced.text)
}
hasPendingUtterance = false
}
// guardrail: อย่า commit ซ้ำหาก joint ยิง EOU ไปแล้ว
if partials.contains(where: { $0.isFinal }) {
hasPendingUtterance = false
}
DictateDemo — แอปตัวอย่างเมนูบาร์ macOS
DictateDemo เป็น agent บนเมนูบาร์ macOS แบบครบเครื่องที่สร้างบน session สตรีมมิ่ง รันเป็นแอปเบื้องหลัง ถอดเสียงจากไมโครโฟนพร้อมผลลัพธ์บางส่วนแบบสด คอมมิตประโยคอัตโนมัติเมื่อเจอ EOU หรือ VAD เงียบ และ paste ผลลัพธ์ไปยังแอปที่อยู่ด้านหน้า
- แอปเมนูบาร์พร้อมฮอตคีย์ระดับระบบ
Cmd+Shift+D - ผลลัพธ์บางส่วนแบบสดพร้อม HUD ลอยและตัวแสดงระดับเสียง
- Force-finalize ที่ป้องกันด้วย VAD (แพทเทิร์น production ด้านบน)
- Paste ไปยังแอปด้านหน้าด้วย
Cmd+Shift+V - โมเดลดาวน์โหลดอัตโนมัติเมื่อเปิดครั้งแรก (~120 MB)
cd Examples/DictateDemo
swift build
.build/debug/DictateDemo
การ implement เต็มอยู่ใน Examples/DictateDemo/DictateDemo/DictateViewModel.swift ได้แก่ audio sink นอก main thread พร้อมบัฟเฟอร์ที่ป้องกันด้วย lock, ตัวจับเวลา tick ทุก 300 ms เพื่อระบายบัฟเฟอร์, Silero VAD พร้อมการส่งต่อ leftover sample และ force-finalize ที่มี guard เทสต์ regression ที่จับคู่กันใน Examples/DictateDemo/Tests/DictateDemoTests.swift ครอบคลุมสถานการณ์หลายประโยค EOU ติดขัด และความเงียบที่มีสัญญาณรบกวน
สตรีมมิ่งเทียบกับ Parakeet แบบ batch
| Parakeet-EOU-120M (สตรีมมิ่ง) | Parakeet TDT 0.6B (batch) | |
|---|---|---|
| กรณีใช้งาน | การบอกพิมพ์สด คำบรรยายเรียลไทม์ | การถอดเสียงไฟล์ งาน offline |
| Decoder | RNN-T + หัว EOU | Token-and-Duration Transducer |
| ขนาด chunk | สตรีมมิ่ง 640 ms | ทั้งไฟล์เป็น batch |
| หน่วยความจำน้ำหนัก | ~120 MB | 500 MB |
| Throughput | ~18 เท่าของเรียลไทม์ | ~32 เท่าของเรียลไทม์ |
| Latency | ผลลัพธ์บางส่วน ~340 ms | เฉพาะตอนจบไฟล์ |
…คุณต้องการผลลัพธ์บางส่วนก่อนที่ผู้ใช้จะพูดจบ สำหรับการถอดเสียงไฟล์เป็น batch Parakeet TDT 0.6B ที่ใหญ่กว่าจะเร็วกว่าแบบ end-to-end และแม่นยำกว่า ทั้งสองโมเดลใช้ vocabulary SentencePiece เดียวกัน คุณจึงสลับระหว่างกันได้โดยไม่ต้องเปลี่ยน tokenization