SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-13

วิธีรัน 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 วินาทีเศษ

ChartWhisper small on CPU, 13 minutes of audio (published project figures)
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 ต้องการพื้นที่ดิสก์เท่าใด

Chartfaster-whisper model weights on disk (Systran CTranslate2 conversions, float16)
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 5000
curl -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.target

sudo 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-cpu

Named 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/models
curl 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 ถึงจะคุ้มค่า?

ChartWhisper large-v2 on an RTX 3070 Ti, 13 minutes of audio (published project figures)
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 cudnnCreateTensorDescriptor

CTranslate2 เวอร์ชัน 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 ไว้ด้านหน้าเพื่อความปลอดภัย

#whisper#tts#piper#speech-to-text#self-hosted-ai