Bối cảnh & Tại sao nên tự host Kokoro-TTS
Nửa năm trước, dàn bot tổng đài và đọc tin vắn tự động của team mình ngốn hơn 2.400 USD tiền API mỗi tháng cho ElevenLabs và Azure Speech. Hóa đơn tăng vọt là một chuyện. Đau đầu hơn là độ trễ mạng chập chờn từ 1,2s đến 1,8s mỗi lượt gọi, cộng thêm rủi ro rò rỉ dữ liệu khách hàng. Tự dựng một cụm TTS nội bộ trên Linux là lối thoát duy nhất.
Bọn mình từng thử Bark nhưng nó ngốn RAM kinh khủng, chỉ số RTF (Real-Time Factor) vọt lên tận 4.0 trên CPU. Chuyển sang Coqui XTTS-v2 thì chất lượng rất ổn, nhưng lại đòi tối thiểu 4GB VRAM và tốc độ sinh âm thanh vẫn trễ nhịp với luồng thoại thời gian thực. Cuối cùng, Kokoro-TTS giải quyết trọn vẹn bài toán. Mô hình này chỉ vỏn vẹn 82 triệu tham số (82M parameters). Giọng đọc tự nhiên đáng kinh ngạc, đặc biệt là chạy mượt mà ngay trên CPU thường mà không cần GPU đắt đỏ.
Nếu bạn muốn làm chủ hoàn toàn pipeline âm thanh và cắt đứt sự phụ thuộc vào các dịch vụ cloud, Kokoro-TTS là lựa chọn đáng tiền nhất hiện nay.
Cài đặt môi trường và Kokoro-TTS trên Linux
Môi trường chuẩn trong bài viết là Ubuntu 22.04 LTS (bạn dùng Debian 12 cũng làm tương tự). Do Kokoro dựa vào espeak-ng để tách âm vị (phonemization), chúng ta cần cài các package C/C++ nền tảng trước.
1. Cài đặt các gói phụ thuộc hệ điều hành
sudo apt-get update && sudo apt-get install -y \
python3-pip \
python3-venv \
espeak-ng \
ffmpeg \
libsndfile1
2. Khởi tạo môi trường ảo Python và cài đặt thư viện
Đừng bao giờ cài thẳng vào Python hệ thống. Hãy cô lập riêng bằng venv:
mkdir -p /opt/kokoro-service && cd /opt/kokoro-service
python3 -m venv venv
source venv/bin/activate
# Cài PyTorch bản CPU để tối ưu chi phí server
pip install --upgrade pip
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
# Cài đặt Kokoro, soundfile và numpy để ghép mảng âm thanh
pip install kokoro soundfile numpy
Nếu server gắn sẵn GPU NVIDIA (ví dụ T4 hoặc RTX 3060), bạn chỉ cần đổi lệnh PyTorch sang bản CUDA (như cu121). Tốc độ render khi đó sẽ nhảy vọt gấp 5 đến 8 lần.
Xây dựng REST API Service với FastAPI
Sau khi chuẩn bị xong thư viện, ta viết script sinh audio và đóng gói thành REST API bằng FastAPI. Nhờ đó, các microservice nội bộ dễ dàng gọi qua HTTP POST.
1. Thử nghiệm sinh giọng nói cơ bản bằng Python
Tạo file kiểm tra nhanh test_speech.py:
import soundfile as sf
from kokoro import KPipeline
# Khởi tạo pipeline tiếng Anh Mỹ ('a') hoặc Anh Anh ('b')
pipeline = KPipeline(lang_code='a')
text = "Kokoro is an efficient text to speech model running entirely on premise."
# Dùng voice mẫu af_heart (nữ, tông ấm, truyền cảm)
generator = pipeline(text, voice='af_heart', speed=1.0, split_pattern=r'\n+')
for i, (gs, ps, audio) in enumerate(generator):
sf.write(f'output_{i}.wav', audio, 24000)
print(f"Đã xuất file output_{i}.wav | Số samples: {len(audio)}")
Chạy lệnh python test_speech.py. Trong lần đầu, script sẽ tự kéo checkpoint khoảng 330MB từ Hugging Face về thư mục cache ~/.cache/huggingface/hub.
2. Đóng gói thành FastAPI Service hoàn chỉnh
Cài thêm các package cho web server:
pip install fastapi uvicorn pydantic
Tạo file app.py xử lý ghép các đoạn audio array thành một file WAV hoàn chỉnh:
import io
import numpy as np
import soundfile as sf
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel
from kokoro import KPipeline
app = FastAPI(title="Kokoro TTS Private API")
# Nạp model một lần duy nhất vào bộ nhớ khi boot app
pipeline = KPipeline(lang_code='a')
class TTSRequest(BaseModel):
text: str
voice: str = "af_heart"
speed: float = 1.0
@app.post("/v1/audio/speech")
def generate_speech(req: TTSRequest):
clean_text = req.text.strip()
if not clean_text:
raise HTTPException(status_code=400, detail="Text cannot be empty")
try:
generator = pipeline(clean_text, voice=req.voice, speed=req.speed)
segments =
if not segments:
raise HTTPException(status_code=500, detail="Failed to synthesize audio")
full_audio = np.concatenate(segments)
buffer = io.BytesIO()
sf.write(buffer, full_audio, 24000, format='WAV')
buffer.seek(0)
return Response(content=buffer.read(), media_type="audio/wav")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
def health_check():
return {"status": "ok", "model": "Kokoro-82M"}
3. Quản lý tiến trình chạy ngầm với Systemd
Để service tự phục hồi khi crash hoặc reboot server, hãy tạo unit file tại /etc/systemd/system/kokoro-tts.service:
[Unit]
Description=Kokoro TTS Offline API Service
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/opt/kokoro-service
ExecStart=/opt/kokoro-service/venv/bin/uvicorn app:app --host 0.0.0.0 --port 8080 --workers 1
Restart=always
RestartSec=5
Environment="PYTHONUNBUFFERED=1"
[Install]
WantedBy=multi-user.target
Chú ý thông số --workers 1. Nếu chạy CPU hạn chế hoặc dùng GPU, giữ 1 worker giúp tránh việc nhân bản model làm tràn RAM/VRAM.
Kích hoạt service:
sudo systemctl daemon-reload
sudo systemctl enable kokoro-tts
sudo systemctl start kokoro-tts
Kiểm thử & Đánh giá hiệu năng thực tế
Giờ là lúc đo đạc thông số thực chiến xem hệ thống đáp ứng tải ra sao.
1. Test độ trễ phản hồi bằng cURL
Chạy lệnh test đo tổng thời gian nhận file:
time curl -X POST "http://localhost:8080/v1/audio/speech" \
-H "Content-Type: application/json" \
-d '{"text": "Your order number 1024 has been processed successfully.", "voice": "af_heart", "speed": 1.0}' \
--output order_success.wav
Kết quả trên con VPS Hetzner 4 vCPU (x86_64) giá 12 EUR/tháng: câu thoại 10 từ mất đúng 260ms để sinh xong file audio 3,2 giây. Chỉ số RTF đạt mức xấp xỉ 0.08. Nghĩa là tốc độ sinh tiếng nhanh gấp 12 lần tốc độ nói thực tế.
2. Mức tiêu thụ tài nguyên phần cứng
Theo dõi process đang chạy:
# Xem log realtime
journalctl -u kokoro-tts -f
# Kiểm tra RAM và CPU tiêu thụ
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | grep uvicorn
Suốt 6 tháng vận hành liên tục, tiến trình Uvicorn ăn cố định khoảng 480MB đến 550MB RAM ở chế độ CPU. Không hề có dấu hiệu rò rỉ bộ nhớ (leak memory). Khi đưa vào cụm production, bạn nên đặt thêm Nginx reverse proxy phía trước kèm cấu hình rate limiting (tối đa 15 request/phút mỗi IP) và giới hạn độ dài payload dưới 500 ký tự mỗi request để tránh nghẽn hàng đợi CPU.

