วิธีรัน Speech-to-Text และ TTS บน VPS ด้วยตัวเอง
เรียนรู้วิธีติดตั้ง Whisper และ Piper บน VPS เพื่อใช้งาน Speech-to-Text และ TTS แบบ self-hosted พร้อมคำนวณการใช้ CPU และดิสก์ รวมถึงวิธีตั้งค่า API ให้รองรับมาตรฐาน OpenAI
สรุปการทำ Speech-to-Text และ TTS แบบ self-hosted
การทำ Speech-to-Text และ TTS (Text-to-Speech) แบบ self-hosted เป็น AI ประเภทที่ประหยัดค่าใช้จ่ายที่สุดที่คุณสามารถรันบนเซิร์ฟเวอร์ส่วนตัวได้ การถอดความเสียง (Transcription) ใช้ Whisper ผ่านรันไทม์ faster-whisper ส่วนการสังเคราะห์เสียง (Synthesis) ใช้ Piper ทั้งสองอย่างทำงานได้บน CPU VPS ทั่วไปโดยไม่จำเป็นต้องมี GPU โมเดล Whisper ขนาดเล็กใช้พื้นที่ดิสก์ประมาณ 484 MB และไฟล์เสียงของ Piper หนึ่งไฟล์มีขนาดไม่ถึง 150 MB
นี่คือเหตุผลที่งานด้านเสียงเป็นจุดเริ่มต้นที่ดีที่สุด การสร้างภาพแบบ self-hosted และ การสร้างวิดีโอแบบ self-hosted ต่างต้องการ GPU ก่อนจึงจะใช้งานได้จริง แต่สำหรับงานด้านเสียงนั้นไม่จำเป็น
คู่มือนี้ครอบคลุมทั้งสองทิศทางและเลเยอร์ที่เชื่อมต่อเข้าด้วยกัน Whisper แปลงเสียงเป็นข้อความ Piper แปลงข้อความเป็นเสียง โดยมี HTTP server ที่รองรับมาตรฐาน OpenAI วางอยู่ด้านหน้า ทำให้ไคลเอนต์ที่มีอยู่เดิมสามารถชี้มายังเซิร์ฟเวอร์ของคุณได้โดยไม่ต้องแก้ไขอะไรนอกจาก base URL
ทุกเวอร์ชันที่ระบุในที่นี้เป็นเวอร์ชันล่าสุด ณ เดือนสิงหาคม 2026
เหตุใดจึงเลือก faster-whisper แทนแพ็กเกจ Whisper อ้างอิง
แพ็กเกจ whisper ของ OpenAI รันโมเดลบน PyTorch ส่วน faster-whisper รันน้ำหนักโมเดลชุดเดียวกันบน CTranslate2 ซึ่งเป็น inference engine ที่เขียนขึ้นสำหรับโมเดล transformer โดยเฉพาะ น้ำหนักโมเดลนั้นเหมือนกันทุกประการ ดังนั้นผลลัพธ์การถอดความจึงเหมือนกัน ความแตกต่างทั้งหมดอยู่ที่ runtime
CTranslate2 จะทำ quantization น้ำหนักโมเดลในขณะโหลด ซึ่งเป็นเหตุผลว่าทำไม compute_type="int8" จึงเป็นเพียงอาร์กิวเมนต์หนึ่ง ไม่ใช่ขั้นตอนการแปลงไฟล์แยกต่างหาก นอกจากนี้ยังไม่มี dependency ของ PyTorch อีกด้วย pip install faster-whisper จะดึง CTranslate2, tokenizer และ PyAV สำหรับการถอดรหัสเสียงมาด้วย ทำให้ virtual environment มีขนาดเพียงหลักร้อยเมกะไบต์ แทนที่จะเป็นหลายกิกะไบต์ บน VPS ที่มีดิสก์ขนาด 40 GB ช่องว่างนี้คือความแตกต่างระหว่างการใช้งานที่สะดวกสบายกับการที่พื้นที่เต็ม
นี่คือตัวเลขที่เผยแพร่โดยโครงการสำหรับโมเดล small บน CPU การทดสอบ benchmark นี้ถอดความเสียงความยาว 13 นาทีโดยใช้ 8 เธรดบน Intel Core i7-12700K คอลัมน์ x_realtime คือความยาวเสียง 13 นาทีหารด้วยเวลาที่วัดได้จริง: 7.6 หมายความว่าการบันทึกเสียง 13 นาทีใช้เวลาประมวลผลเพียง 1 นาที 40 วินาทีเศษ
The data behind this chart
[
{
"label": "openai/whisper, fp32",
"x_realtime": 1.9,
"seconds": 418,
"memory_mb": "2,335"
},
{
"label": "whisper.cpp, fp32",
"x_realtime": 6.2,
"seconds": 125,
"memory_mb": "1,049"
},
{
"label": "faster-whisper, fp32",
"x_realtime": 5.0,
"seconds": 157,
"memory_mb": "2,257"
},
{
"label": "faster-whisper, int8",
"x_realtime": 7.6,
"seconds": 102,
"memory_mb": "1,477"
},
{
"label": "faster-whisper, int8, batch 8",
"x_realtime": 15.3,
"seconds": 51,
"memory_mb": "3,608"
}
]ลองพิจารณาแถวที่สองตามความเป็นจริง ที่ระดับ fp32 นั้น whisper.cpp ทำงานได้เร็วกว่า faster-whisper บน CPU นี้ โดยอยู่ที่ 6.2x เทียบกับ 5.0x และใช้หน่วยความจำน้อยกว่าครึ่งหนึ่ง มันคือตระกูล ggml เดียวกับที่อยู่เบื้องหลัง ฝั่ง llama.cpp ของ stack LLM ในเครื่อง faster-whisper จะทำความเร็วได้เหนือกว่าเมื่อเปิดใช้งาน int8 และการทำ batching โดยทำความเร็วได้ถึง 15.3x และใช้ RAM ไป 3,608 MB เพื่อแลกกับประสิทธิภาพนั้น ดังนั้น: เลือก faster-whisper หากต้องการ Python API และการทำ batching และเลือก whisper.cpp เมื่อ RAM เป็นข้อจำกัดที่คุณไม่สามารถขยายได้
แถวแรกคือประเด็นสำคัญของส่วนนี้ แพ็กเกจมาตรฐานทำงานที่ความเร็ว 1.9x ของเวลาจริงบนเครื่องเดียวกัน ซึ่งหมายความว่าเสียงความยาวหนึ่งชั่วโมงต้องใช้เวลา CPU ถึงครึ่งชั่วโมงในการประมวลผล
โมเดล Whisper ต้องการพื้นที่ดิสก์เท่าใด
The data behind this chart
[
{
"label": "tiny",
"weights_mb": 75.5
},
{
"label": "base",
"weights_mb": 145
},
{
"label": "small",
"weights_mb": 484
},
{
"label": "medium",
"weights_mb": "1,530"
},
{
"label": "large-v3",
"weights_mb": "3,090"
}
]นี่คือข้อมูลการแปลงโมเดลสำหรับ CTranslate2 ที่เผยแพร่ไว้ในรูปแบบ float16 โปรดสังเกตว่าสิ่งที่ compute_type="int8" ไม่ได้ทำคือการลดขนาดไฟล์ดาวน์โหลด น้ำหนักของโมเดลจะถูกดาวน์โหลดมาในรูปแบบ float16 และ CTranslate2 จะทำการ quantize ในหน่วยความจำขณะโหลด ดังนั้นโมเดล large-v3 จึงใช้พื้นที่ 3,090 MB บนดิสก์ ไม่ว่าคุณจะรันด้วย float16 หรือ int8 ก็ตาม การใช้ int8 จะช่วยประหยัด RAM และเพิ่มความเร็ว แต่ไม่ได้ช่วยประหยัดพื้นที่ดิสก์
โมเดลจะถูกดาวน์โหลดลงใน ~/.cache/huggingface/hub เมื่อมีการเรียกใช้งานครั้งแรก หากคุณใช้ VPS ที่มี root volume ขนาดเล็ก ให้กำหนดเส้นทางไปยังตำแหน่งที่มีพื้นที่เพียงพอโดยใช้ argument download_root หรือ environment variable HF_HOME มิฉะนั้นการรันครั้งแรกอาจทำให้ดิสก์เต็มและกระบวนการทำงานจะหยุดลงกลางคันระหว่างการดาวน์โหลด
การติดตั้ง faster-whisper โดยไม่กระทบต่อระบบ Python
Ubuntu 24.04 และ Debian 13 กำหนดให้ Python ของระบบเป็นแบบจัดการจากภายนอก (externally managed) การรัน pip install faster-whisper นอกสภาพแวดล้อมเสมือน (virtual environment) จะหยุดทำงานทันทีพร้อมข้อความ:
error: externally-managed-environmentนี่คือกลไกของระบบจัดการแพ็กเกจที่ป้องกันไฟล์ซึ่ง apt เป็นเจ้าของ ให้ใช้สภาพแวดล้อมเสมือนแทน
sudo apt update
sudo apt install -y python3-venv ffmpeg
python3 -m venv ~/stt
~/stt/bin/pip install --upgrade pip
~/stt/bin/pip install faster-whisper==1.2.1เวอร์ชัน 1.2.1 คือรุ่นปัจจุบันที่เผยแพร่ในเดือนตุลาคม 2025 โดย faster-whisper จะถอดรหัสเสียงผ่าน PyAV ซึ่งรวมไลบรารี ffmpeg มาให้ในตัว จึงสามารถอ่านไฟล์ mp3 หรือ m4a ได้โดยไม่ต้องใช้ไบนารี ffmpeg แยกต่างหาก ส่วนแพ็กเกจ ffmpeg ด้านบนนั้นมีไว้สำหรับการประมวลผลวิดีโอในขั้นตอนถัดไปของคู่มือนี้
ตรวจสอบการติดตั้งก่อนที่คุณจะดาวน์โหลดไฟล์น้ำหนัก (weights) ขนาด 3 กิกะไบต์:
~/stt/bin/python -c "from faster_whisper import WhisperModel; print('ok')"หากปรากฏบรรทัดที่อ่านว่า ok แสดงว่าติดตั้งสำเร็จแล้ว หากปรากฏ ImportError ที่ระบุชื่อ ctranslate2 แสดงว่าไฟล์ wheel สำหรับสถาปัตยกรรมของคุณไม่ได้ถูกติดตั้ง ซึ่งมักเกิดขึ้นบนอิมเมจ ARM แบบ 32 บิต
การถอดความบันทึกการประชุมหรือบันทึกเสียง
บันทึกไฟล์นี้เป็น transcribe.py:
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cpu", compute_type="int8", cpu_threads=4)
segments, info = model.transcribe("meeting.m4a", beam_size=5, vad_filter=True)
print("language: %s (%.2f)" % (info.language, info.language_probability))
for segment in segments:
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))รันด้วยคำสั่ง ~/stt/bin/python transcribe.py การรันครั้งแรกจะทำการดาวน์โหลดน้ำหนัก (weights) ของโมเดล ดังนั้นจะไม่มีข้อความแสดงผลออกมาในช่วงแรก หลังจากนั้นโมเดลจะโหลดจากแคชภายในเวลาไม่กี่วินาที
segments เป็นตัวสร้าง (generator) ดังนั้นการถอดความจะยังไม่เริ่มต้นจนกว่าคุณจะวนลูป (iterate) ผ่านตัวแปรนั้น ผู้ใช้มักจะจับเวลาการเรียก transcribe() แล้วเห็นว่ามันคืนค่าทันที จึงเข้าใจผิดว่ามีบางอย่างทำงานผิดพลาด ซึ่งความจริงแล้วไม่มีอะไรผิดพลาด งานประมวลผลจะเกิดขึ้นจริงภายในลูป
vad_filter=True จะรัน Silero VAD (การตรวจจับกิจกรรมเสียง) ก่อน และตัดช่วงที่เงียบออกไปก่อนที่ Whisper จะได้รับข้อมูล ในการบันทึกการประชุมที่มีช่วงเว้นว่างยาวนาน นี่คือวิธีเพิ่มความเร็วที่เห็นผลชัดเจนที่สุด เพราะ Whisper จะไม่ต้องเสียเวลาประมวลผลช่วงเสียงที่ไม่มีการพูดเลย นอกจากนี้ยังช่วยป้องกันปัญหาการวนซ้ำประโยคเดิมที่ Whisper มักจะสร้างขึ้นเมื่อได้รับข้อมูลที่เป็นความเงียบและพยายามค้นหาคำพูดจากช่วงนั้น
ตั้งค่า cpu_threads ให้ตรงกับจำนวนคอร์ที่คุณมีอยู่จริง การตั้งค่าให้สูงกว่าจำนวน vCPU จะทำให้การถอดความช้าลง เนื่องจากเธรดส่วนเกินจะแย่งชิงการใช้งานคอร์เดียวกัน และตัวจัดตารางเวลา (scheduler) จะต้องเสียทรัพยากรไปกับการสลับเธรดทุกครั้ง
คำบรรยายสำหรับคลังสื่อ Jellyfin
Jellyfin สามารถอ่านไฟล์คำบรรยายภายนอกที่วางไว้คู่กับไฟล์วิดีโอและใช้ชื่อไฟล์เดียวกันได้ ดังนั้น Movie (2019).en.srt ที่วางอยู่ข้าง Movie (2019).mkv จะปรากฏเป็นแทร็กภาษาอังกฤษโดยไม่ต้องทำการ transcoding หรือสร้างคลังสื่อใหม่
ให้แยกเสียงออกมาก่อน Whisper จะทำการ resample ข้อมูลทั้งหมดเป็น 16 kHz mono ภายในตัวอยู่แล้ว ดังนั้นการส่งไฟล์ที่เป็น 16 kHz mono ให้ตั้งแต่แรกจะช่วยลดภาระการประมวลผลของทั้งสองฝั่ง:
ffmpeg -i "Movie (2019).mkv" -vn -ac 1 -ar 16000 -c:a pcm_s16le "Movie (2019).wav"faster-whisper ไม่มีฟังก์ชันเขียนไฟล์ SRT มาให้ คุณจึงต้องจัดรูปแบบ segment ด้วยตนเอง ให้บันทึกโค้ดนี้เป็น srt.py:
import sys
from faster_whisper import WhisperModel
def ts(seconds):
ms = int(round(seconds * 1000))
hours, ms = divmod(ms, 3600000)
minutes, ms = divmod(ms, 60000)
secs, ms = divmod(ms, 1000)
return "%02d:%02d:%02d,%03d" % (hours, minutes, secs, ms)
model = WhisperModel("small", device="cpu", compute_type="int8")
segments, info = model.transcribe(sys.argv[1], vad_filter=True)
with open(sys.argv[2], "w", encoding="utf-8") as out:
for index, segment in enumerate(segments, start=1):
out.write("%d\n" % index)
out.write("%s --> %s\n" % (ts(segment.start), ts(segment.end)))
out.write("%s\n\n" % segment.text.strip())จากนั้นให้วนลูปจัดการคลังสื่อของคุณ:
for f in /srv/media/films/*.mkv; do
ffmpeg -nostdin -y -i "$f" -vn -ac 1 -ar 16000 -c:a pcm_s16le "${f%.mkv}.wav"
nice -n 15 ~/stt/bin/python srt.py "${f%.mkv}.wav" "${f%.mkv}.en.srt"
rm -f "${f%.mkv}.wav"
done-nostdin ไม่ใช่สิ่งที่ใส่ไว้เฉยๆ หากไม่มีคำสั่งนี้ ffmpeg จะอ่านข้อมูลจาก standard input ของลูป ทำให้มันอ่านรายชื่อไฟล์ที่เหลือไปจนหมด และลูปจะหยุดทำงานหลังจากประมวลผลภาพยนตร์ไปเพียงเรื่องเดียวโดยไม่มีข้อความแจ้งเตือนข้อผิดพลาดใดๆ
ควรประเมินเวลาก่อนเริ่มดำเนินการ ตามตัวเลข 7.6x ที่ระบุไว้ข้างต้น ภาพยนตร์ความยาว 100 นาทีจะใช้เวลา CPU ประมาณ 13 นาที ดังนั้นคลังสื่อที่มีภาพยนตร์ 50 เรื่องจึงเป็นงานที่ต้องใช้เวลาทำข้ามคืน หากใช้งานบนแผน vCPU แบบแชร์ ความเร็วจะช้ากว่านั้น ซึ่งนั่นคือเหตุผลที่มี nice ไว้ เพื่อให้กระบวนการสร้างคำบรรยายยอมหลีกทางให้กับงานอื่นที่เซิร์ฟเวอร์กำลังประมวลผลอยู่ตามปกติ
การแปลงข้อความเป็นเสียงด้วย Piper
Piper เป็นเอนจิน Neural Text-to-Speech ที่รันโมเดลเสียงแบบ ONNX บน CPU โดยมีการฝัง espeak-ng มาในตัวเพื่อแปลงข้อความเป็นหน่วยเสียง (phonemes) ทำให้ไม่จำเป็นต้องติดตั้งตัวแปลงหน่วยเสียงแยกต่างหาก รุ่นปัจจุบันคือ 1.6.0 ซึ่งเผยแพร่เมื่อเดือนกรกฎาคม 2026
python3 -m venv ~/tts
~/tts/bin/pip install piper-tts==1.6.0
~/tts/bin/python -m piper.download_voices en_US-lessac-medium
~/tts/bin/python -m piper -m en_US-lessac-medium -f test.wav -- 'This is a test.'download_voices จะเขียนไฟล์สองไฟล์ลงในไดเรกทอรีที่ทำงานอยู่ ได้แก่ไฟล์น้ำหนัก .onnx และไฟล์ .onnx.json config ซึ่งเก็บค่าอัตราสุ่มตัวอย่าง (sample rate) และรายการผู้พูด ไฟล์เหล่านี้ต้องอยู่ด้วยกันเสมอ หากคุณเก็บไฟล์เสียงไว้ในตำแหน่งที่กำหนดไว้ตายตัว ให้ส่งค่า --data-dir ไปยังคำสั่งทั้งสอง เนื่องจากโปรแกรมเล่นเสียงจะมองหาไฟล์ในไดเรกทอรีที่ทำงานอยู่เป็นค่าเริ่มต้นและจะไม่พบไฟล์เสียงหากไม่ได้อยู่ในนั้น
-- ที่อยู่หน้าข้อความก็มีความสำคัญเช่นกัน หากไม่มีตัวคั่นนี้ ประโยคใดก็ตามที่ขึ้นต้นด้วยเครื่องหมายยัติภังค์จะถูกตีความว่าเป็นตัวเลือกของบรรทัดคำสั่ง (command-line option)
ไฟล์เสียงมีให้เลือกหลายระดับคุณภาพ ไฟล์เสียงภาษาอังกฤษระดับกลางมีขนาดประมาณ 60 MB และระดับสูงจะมีขนาดประมาณสองเท่า คุณภาพที่สูงขึ้นหมายถึงโมเดลที่มีขนาดใหญ่ขึ้นและใช้ CPU ต่อวินาทีของเสียงมากขึ้น ไม่ใช่การเปลี่ยนผู้พูด
อย่าเรียกใช้ CLI ภายในลูป เนื่องจากโปรแกรมจะโหลดโมเดลทุกครั้งที่เรียกใช้งาน และการโหลดโมเดลจะใช้ทรัพยากรสูงกว่าการประมวลผลประโยคสั้นๆ ให้รัน HTTP server แทน:
~/tts/bin/pip install 'piper-tts[http]==1.6.0'
~/tts/bin/python -m piper.http_server -m en_US-lessac-medium --host 127.0.0.1 --port 5000curl -X POST -H 'Content-Type: application/json' \
-d '{ "text": "This is a test." }' \
-o test.wav localhost:5000/synthesizeหาก test.wav ที่คุณเล่นได้แสดงว่าระบบทำงานปกติ endpoint ดังกล่าวเป็นรูปแบบของ Piper เอง ไม่ใช่ของ OpenAI ดังนั้นไคลเอนต์ที่คาดหวังรูปแบบ /v1/audio/speech จะไม่สามารถสื่อสารกับมันได้ ส่วนถัดไปจะอธิบายวิธีแก้ไขปัญหานี้
ให้รันโปรแกรมทิ้งไว้ด้วย unit file แทนการรันในเทอร์มินัลที่คุณจะต้องปิด:
[Unit]
Description=Piper text to speech HTTP server
After=network-online.target
[Service]
User=piper
ExecStart=/home/piper/tts/bin/python -m piper.http_server -m en_US-lessac-medium --data-dir /home/piper/voices --host 127.0.0.1 --port 5000
Restart=on-failure
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now piper จะเริ่มการทำงานของโปรแกรมและทำให้มันกลับมาทำงานใหม่หลังจากรีบูต หากคำสั่ง curl ด้านบนไม่ส่งค่าใดๆ กลับมา journalctl -u piper -n 50 จะระบุสาเหตุ ซึ่งสาเหตุที่พบบ่อยที่สุดคือไฟล์เสียงหายไป
รูปแบบเซิร์ฟเวอร์ที่รองรับ OpenAI
ซอฟต์แวร์ส่วนใหญ่ที่จัดการด้านเสียงรองรับ OpenAI audio API อยู่แล้ว โดยใช้การส่ง multipart POST ไปยัง /v1/audio/transcriptions สำหรับไฟล์ และ JSON POST ไปยัง /v1/audio/speech สำหรับประโยค หากคุณให้บริการทั้งสอง path นี้ด้วยตนเอง ไคลเอนต์จะต้องการการตั้งค่าเพียงอย่างเดียวคือการเปลี่ยน base URL
Speaches เป็นเซิร์ฟเวอร์ตัวหนึ่งที่รองรับทั้งสองทิศทาง โดยรัน faster-whisper สำหรับการถอดความ และใช้ Piper หรือ Kokoro สำหรับการสังเคราะห์เสียงภายใต้ path ของ OpenAI รุ่นปัจจุบันคือ v0.9.0-rc.3 จากเดือนธันวาคม 2025 ซึ่งยังไม่ถึงเวอร์ชัน 1.0 ดังนั้นควรระบุ image tag ให้ชัดเจนและอ่านบันทึกประจำรุ่น (release notes) ก่อนทำการอัปเกรด
curl --silent --remote-name https://raw.githubusercontent.com/speaches-ai/speaches/master/compose.yaml
curl --silent --remote-name https://raw.githubusercontent.com/speaches-ai/speaches/master/compose.cpu.yaml
export COMPOSE_FILE=compose.cpu.yaml
docker compose up --detachรูปแบบ single-container หากคุณไม่ต้องการเก็บไฟล์ compose:
docker run --rm --detach --publish 8000:8000 --name speaches \
--volume hf-hub-cache:/home/ubuntu/.cache/huggingface/hub \
ghcr.io/speaches-ai/speaches:latest-cpuNamed volume คือส่วนที่ผู้ใช้มักลืม หากไม่มีส่วนนี้ แคชของโมเดลจะอยู่ในคอนเทนเนอร์ ทำให้ทุกครั้งที่รีสตาร์ท ระบบจะต้องดาวน์โหลดไฟล์น้ำหนัก (weights) ขนาดหลายกิกะไบต์ใหม่ก่อนที่จะตอบสนองคำขอแรก
Speech จำเป็นต้องดาวน์โหลดเสียงก่อนที่ /v1/audio/speech จะตอบสนอง:
uvx speaches-cli model download speaches-ai/Kokoro-82M-v1.0-ONNXหลังจากนั้น ไคลเอนต์ OpenAI ใดๆ ก็สามารถใช้งานได้โดยการเปลี่ยน base URL:
from pathlib import Path
from openai import OpenAI
openai = OpenAI(base_url="http://localhost:8000/v1", api_key="cant-be-empty")
res = openai.audio.speech.create(
model="speaches-ai/Kokoro-82M-v1.0-ONNX",
voice="af_heart",
input="Hello, world!",
response_format="mp3",
speed=1,
)
with Path("output.mp3").open("wb") as f:
f.write(res.response.read())จำเป็นต้องใส่ค่า placeholder api_key เนื่องจากไลบรารีไคลเอนต์ของ OpenAI จะปฏิเสธการส่งคำขอหากไม่มีค่านี้ เซิร์ฟเวอร์จะเพิกเฉยต่อค่าดังกล่าวจนกว่าคุณจะตั้งค่าคีย์จริง
vox-box เป็นอีกโครงการที่น่ากล่าวถึง โดยเป็นแพ็กเกจ Python แทนที่จะเป็นคอนเทนเนอร์ และให้บริการผ่าน path เดียวกันโดยใช้ Whisper, FunASR, Bark, Dia หรือ CosyVoice เวอร์ชันปัจจุบันคือ 0.0.21 จากเดือนธันวาคม 2025 และต้องการ Python 3.10 ขึ้นไป
python3 -m venv ~/voice
~/voice/bin/pip install vox-box==0.0.21
~/voice/bin/vox-box start --huggingface-repo-id Systran/faster-whisper-small \
--data-dir ~/voice/data --host 127.0.0.1 --port 8010ตัวอย่างของโครงการผูกไว้กับพอร์ต 80 ซึ่งต้องใช้สิทธิ์ root การใช้พอร์ตสูงผ่าน reverse proxy เป็นรูปแบบที่ดีกว่าบนเซิร์ฟเวอร์ที่ทำงานอื่นด้วย vox-box start รองรับหนึ่งโมเดล ดังนั้นการครอบคลุมทั้งสองทิศทางจึงหมายถึงการรัน instance ที่สองบนพอร์ตที่สองพร้อมกับระบุ speech repo id
สอบถามเซิร์ฟเวอร์ว่าโหลดอะไรไว้บ้าง จากนั้นใช้ id นั้นในฟิลด์ model:
curl http://127.0.0.1:8010/v1/modelscurl http://127.0.0.1:8010/v1/audio/transcriptions \
-H "Content-Type: multipart/form-data" \
-F file="@voice-note.m4a" \
-F model="faster-whisper-small"หากได้รับ JSON body ในรูปแบบ {"text": "..."} แสดงว่า path นั้นทำงานได้ปกติ หากได้รับ 404 ในชื่อโมเดล แสดงว่าคุณเดาชื่อแทนที่จะอ่านผลลัพธ์จาก /v1/models
เซิร์ฟเวอร์เหล่านี้ไม่มีการเปิดใช้งานการยืนยันตัวตนโดยค่าเริ่มต้น ให้ผูกไว้กับ 127.0.0.1 และเข้าถึงผ่าน VPN หรือ reverse proxy ที่มีการร้องขอข้อมูลประจำตัว การเปิด /v1/audio/transcriptions บน public IP จะทำให้ใครก็ตามที่พบสามารถใช้ CPU ของคุณได้ฟรี และพวกเขาจะพบมันอย่างแน่นอน
ส่วนติดต่อผู้ใช้สำหรับระบบผู้ช่วยส่วนตัวแบบ self-hosted
เมื่อทั้งสองทิศทางตอบสนองผ่านเส้นทางของ OpenAI ได้แล้ว ส่วนติดต่อผู้ใช้สำหรับการแชทก็สามารถนำมาใช้งานร่วมกันได้ Open WebUI และทางเลือกอื่น รองรับการระบุ base URL ที่เข้ากันได้กับ OpenAI สำหรับเสียงในการตั้งค่า ทำให้สามารถ รัน LLM ภายในเครื่องด้วย Ollama และปิดลูปการทำงานของเสียงได้ทั้งสองฝั่ง
ควรประเมินค่าความหน่วง (latency) ตามความเป็นจริง เนื่องจากทั้งสามขั้นตอนจะทำงานต่อเนื่องกันบนคอร์ประมวลผลชุดเดียวกัน คำถามความยาว 10 วินาทีจะใช้เวลาถอดความเป็นข้อความประมาณ 1.3 วินาที ตามตัวเลข 7.6x ที่ระบุไว้ข้างต้น ซึ่งนั่นเป็นเวลาก่อนที่โมเดลจะเริ่มอ่าน token แรกเสียอีก เมื่อรวมเวลาการสร้าง token ของ CPU เข้าไปด้วย ระยะเวลาการรับส่งข้อมูลจะนานจนผู้ทดสอบอาจเข้าใจผิดว่าระบบค้าง การถอดความแบบ batch บน CPU นั้นทำได้ดี แต่สำหรับการสนทนาโต้ตอบนั้นไม่เหมาะสม และนั่นคือจุดที่ การใช้ VPS ที่มี GPU เริ่มคุ้มค่ากับราคาที่จ่ายไป
เมื่อใดที่ GPU ถึงจะคุ้มค่า?
The data behind this chart
[
{
"label": "openai/whisper, fp16",
"x_realtime": 5.5,
"seconds": 143,
"memory_mb": "4,708"
},
{
"label": "faster-whisper, fp16",
"x_realtime": 12.4,
"seconds": 63,
"memory_mb": "4,525"
},
{
"label": "faster-whisper, int8",
"x_realtime": 13.2,
"seconds": 59,
"memory_mb": "2,926"
},
{
"label": "faster-whisper, int8, batch 8",
"x_realtime": 48.8,
"seconds": 16,
"memory_mb": "4,500"
}
]บน GPU โมเดลขนาดใหญ่ที่ระดับ int8 ทำงานได้ที่ 13.2 เท่าของเวลาจริง โดยใช้ VRAM ไป 2,926 MB และการทำ batching จะช่วยให้ทำได้ถึง 48.8 เท่า โปรดสังเกตสิ่งที่แผนภูมิทั้งสองไม่ได้ระบุไว้ให้ดี ทั้งสองรันโมเดลที่ต่างกัน โดยแถวของ GPU ใช้ large-v2 ส่วนแถวของ CPU ใช้ขนาด small การมี GPU ไม่ได้ทำให้โมเดลขนาด small เร็วขึ้นหกเท่า แต่มันทำให้โมเดลที่มีความแม่นยำสูงสามารถนำมาใช้งานจริงได้
ที่มาของตัวเลขเหล่านี้
แผนภูมิทั้งสองจำลองผลการทดสอบที่เผยแพร่ใน README ของ faster-whisper โดยใช้ไฟล์เสียงความยาว 13 นาทีเพียงไฟล์เดียว แถวของ CPU ใช้ 8 threads บน Intel Core i7-12700K ส่วนแถวของ GPU ใช้ CUDA 12.4 บน NVIDIA RTX 3070 Ti ที่มี VRAM 8 GB คอลัมน์วินาทีและหน่วยความจำคือตัวเลขที่เผยแพร่อย่างเป็นทางการ ส่วนคอลัมน์ x_realtime คือผลการคำนวณจากเวลาเสียง 780 วินาทีหารด้วยเวลาที่วัดได้จริง แล้วปัดเศษทศนิยมหนึ่งตำแหน่ง คอลัมน์หน่วยความจำคือ RAM ของระบบสำหรับแผนภูมิ CPU และ VRAM สำหรับแผนภูมิ GPU
แผนบริการ vCPU แบบแชร์จะไม่สามารถทำตัวเลขได้เท่ากับ CPU ในการทดสอบนั้น เพราะการทดสอบใช้ 8 threads ของซีพียูเดสก์ท็อปประสิทธิภาพสูงแบบเต็มกำลัง ให้ถือว่า 7.6 เท่าเป็นเพดานสูงสุด และควรวัดผลบนเครื่องของคุณเองด้วย time กับไฟล์บันทึกเสียงจริงก่อนที่จะวางแผนใดๆ
กฎสี่ข้อที่ใช้ได้จริงในทางปฏิบัติ:
- การถอดความบันทึกเสียงของคุณเองเป็นครั้งคราว: ใช้ CPU, โมเดลขนาด small, int8 โดยมี vCPU แบบเฉพาะ 2 คอร์ก็เพียงพอแล้ว
- งาน batch ที่ทำข้ามคืน เช่น การสร้างคำบรรยาย: ใช้ CPU, โมเดลขนาด small หรือ medium, int8 โดยรันผ่าน
nice - งานใดๆ ที่ต้องการการโต้ตอบ หรือการประมวลผลคลังไฟล์ทั้งหมดในคืนเดียว: ใช้ GPU
- Piper: ใช้ CPU เสมอ โมเดลเสียงขนาดนี้แทบไม่ได้รับประโยชน์ใดๆ จาก GPU
หากคุณกำลังพิจารณาว่าฮาร์ดแวร์ชุดเดียวกันนี้สามารถรองรับงานอื่นใดได้อีกบ้าง คำถามในวงกว้างว่าโมเดล AI ใดบ้างที่คุณสามารถ self-host ได้ จะครอบคลุมถึงขนาดโมเดลที่สามารถรันได้และไม่ได้บนฮาร์ดแวร์ของคุณ
รูปแบบความล้มเหลวและข้อความที่คุณจะพบ
error: externally-managed-environment ระหว่างการติดตั้ง ระบบ Python ถูกป้องกันไว้โดยตัวจัดจำหน่าย (distribution) ให้สร้าง virtual environment ตามที่แสดงไว้ด้านบน
cuDNN ไม่ตรงกันบนเครื่อง GPU ความล้มเหลวจะมีลักษณะดังนี้:
Unable to load any of {libcudnn_ops.so.9.1.0, libcudnn_ops.so.9.1, libcudnn_ops.so.9, libcudnn_ops.so}
Invalid handle. Cannot load symbol cudnnCreateTensorDescriptorCTranslate2 เวอร์ชัน 4.5.0 ขึ้นไปต้องการ cuDNN 9 สำหรับ CUDA 12 แต่โฮสต์ของคุณใช้ cuDNN 8 ให้ติดตั้ง cuDNN 9 หรือกำหนดเวอร์ชัน runtime เก่าด้วย pip install --force-reinstall ctranslate2==4.4.0 ให้เลือกทำอย่างใดอย่างหนึ่ง การทำทั้งสองอย่างพร้อมกันจะทำให้คุณกลับไปจุดเริ่มต้น
This CTranslate2 package was not compiled with CUDA support เป็นข้อผิดพลาดที่ต่างออกไปแต่มีลักษณะคล้ายกัน คือ wheel ที่ติดตั้งเป็นรุ่นสำหรับ CPU เท่านั้น ให้สร้าง virtual environment ใหม่บนโฮสต์ GPU แล้วปล่อยให้ pip เลือก wheel ใหม่อีกครั้ง
วลีซ้ำในข้อความที่ถอดความ ประโยคที่วนซ้ำสิบครั้งมักเกิดจากความเงียบหรือดนตรีที่ถูกตีความว่าเป็นคำพูด ให้เปิดใช้งาน vad_filter=True ก่อน หากยังพบปัญหาเดิม ให้ลองฟังส่วนนั้นของเสียง: เสียงที่เกือบเงียบทำให้ Whisper ไม่มีจุดอ้างอิง จึงวนซ้ำคำคาดเดาล่าสุดที่มั่นใจที่สุด
ตรวจพบภาษาผิด Whisper คาดเดาภาษาจาก 30 วินาทีแรกเท่านั้น ค่า info.language_probability ที่ต่ำกว่า 1.0 มากๆ หมายความว่าระบบไม่มั่นใจ ซึ่งมักเกิดขึ้นเมื่อบันทึกเสียงเริ่มต้นด้วยดนตรีหรือมีเสียงพูดแทรก ให้ส่งค่า language="en" หากคุณทราบภาษาที่ถูกต้องอยู่แล้ว
กระบวนการแสดง Killed แล้วหยุดทำงาน นี่คือการทำงานของ kernel out-of-memory killer และ dmesg จะแสดงบรรทัด oom-kill ที่เกี่ยวข้อง โมเดล large-v3 ต้องการหน่วยความจำมากกว่า 3 GB สำหรับ weight เพียงอย่างเดียว ก่อนจะเริ่มประมวลผลจริง บน VPS ขนาด 2 GB โมเดลขนาด small ที่ระดับ int8 คือโมเดลที่ใหญ่ที่สุดที่สามารถใช้งานได้
Piper หาเสียงไม่พบ โปรแกรมเล่นเสียงจะค้นหาใน working directory เว้นแต่คุณจะระบุ --data-dir ให้รันคำสั่ง ls ในไดเรกทอรีที่คุณคาดหวัง และตรวจสอบว่ามีทั้ง .onnx และ .onnx.json อยู่จริง เพราะการขาดอย่างใดอย่างหนึ่งไปจะทำให้เกิดความล้มเหลวเช่นเดียวกับการไม่มีไฟล์เลย
FAQ
ฉันสามารถรัน speech-to-text บน VPS ที่ไม่มี GPU ได้หรือไม่?
ได้ และสำหรับการประมวลผลแบบ batch นี่เป็นทางเลือกที่สมเหตุสมผล หากใช้ faster-whisper กับโมเดลขนาด small ที่ระดับ int8 ผลการทดสอบมาตรฐานของโปรเจกต์ระบุว่าสามารถถอดความเสียงความยาว 13 นาทีได้ใน 102 วินาที บน 8 threads ของ desktop i7 ซึ่งคิดเป็นประมาณ 7.6 เท่าของเวลาจริง โดยใช้ RAM ไป 1,477 MB แผนบริการแบบ shared vCPU จะทำงานช้ากว่านี้ ดังนั้นควรวัดผลบนเครื่องของคุณเอง ส่วนการทำ text-to-speech ด้วย Piper นั้นง่ายกว่ามากและไม่จำเป็นต้องใช้ GPU ในทุกกรณี
ฉันควรใช้ Whisper โมเดลขนาดใด?
ให้เริ่มจากขนาด small ที่ int8 ซึ่งใช้พื้นที่บนดิสก์ 484 MB และจัดการกับเสียงพูดที่ชัดเจนได้ดี ให้ขยับไปใช้ขนาด medium เมื่อพบปัญหาสำเนียงหรือเสียงรบกวนจนยอมรับไม่ได้ และใช้ขนาด large-v3 เฉพาะเมื่อความแม่นยำสำคัญเหนือสิ่งอื่นใด เนื่องจากโมเดลนี้ต้องการพื้นที่ดิสก์ 3,090 MB และหน่วยความจำมากกว่า 3 GB ในทางกลับกัน ขนาด tiny ที่ 75.5 MB มีประโยชน์สำหรับการตรวจจับภาษาและการทดสอบ pipeline เท่านั้น ไม่เหมาะสำหรับบทถอดความที่ต้องนำไปอ่านจริง
ทำไม faster-whisper ถึงทำงานได้เร็วกว่าแพ็กเกจ Whisper ดั้งเดิม?
โมเดลนั้นเหมือนกัน แต่ runtime ต่างกัน แพ็กเกจมาตรฐานรัน Whisper บน PyTorch ในขณะที่ faster-whisper รันน้ำหนัก (weights) เดียวกันบน CTranslate2 ซึ่งเป็น engine ที่สร้างมาเพื่อ transformer inference โดยเฉพาะ โดยจะทำการ quantize น้ำหนักขณะโหลดและไม่จำเป็นต้องติดตั้ง PyTorch ในการทดสอบมาตรฐานบน CPU ที่เผยแพร่ไว้ แพ็กเกจมาตรฐานทำความเร็วได้ 1.9 เท่าของเวลาจริง เทียบกับ 7.6 เท่าของ faster-whisper ที่ int8 โดยใช้หน่วยความจำน้อยกว่า
ทำไมบทถอดความของฉันถึงมีประโยคเดิมซ้ำไปซ้ำมา?
Whisper กำลังตีความความเงียบหรือดนตรีว่าเป็นเสียงพูดและพยายามเดาจากสิ่งที่มั่นใจที่สุดครั้งล่าสุด ให้ตั้งค่า vad_filter=True ในการเรียกใช้งาน transcribe() ซึ่งจะรัน Silero voice activity detection ก่อนเพื่อตัดช่วงความเงียบออกไปก่อนที่โมเดลจะประมวลผล หากยังคงมีการซ้ำ ให้ตรวจสอบระดับเสียง เพราะการบันทึกที่เกือบเงียบตลอดทั้งไฟล์จะทำให้โมเดลไม่มีข้อมูลเพียงพอในการทำงาน
ฉันจะสร้าง audio endpoint ที่รองรับ OpenAI บนเซิร์ฟเวอร์ของตัวเองได้อย่างไร?
ให้รันเซิร์ฟเวอร์ที่รองรับ POST /v1/audio/transcriptions และ POST /v1/audio/speech จากนั้นชี้ client ไปยัง URL นั้นด้วย base URL ใหม่ Speaches เป็นตัวเลือกหนึ่งที่เผยแพร่ในรูปแบบ container image พร้อม build สำหรับ CPU โดยใช้ faster-whisper สำหรับการถอดความและ Piper หรือ Kokoro สำหรับเสียงพูด vox-box เป็นอีกทางเลือกหนึ่งซึ่งติดตั้งด้วย pip install vox-box และเริ่มทำงานด้วย vox-box start --huggingface-repo-id โดยจะให้บริการหนึ่งโมเดลต่อหนึ่ง process ทั้งสองตัวเลือกไม่ได้เปิดใช้งานการยืนยันตัวตนไว้เป็นค่าเริ่มต้น ดังนั้นควร bind ไว้ที่ localhost และวาง proxy หรือ VPN ไว้ด้านหน้าเพื่อความปลอดภัย