speech-to-speech: trợ lý giọng nói chạy local

1. Bối cảnh: vì sao cần một dây chuyền chạy local

Các trợ lý giọng nói thương mại đều vận hành trên hạ tầng đám mây. Mô hình này có ba ràng buộc cố hữu: phụ thuộc kết nối mạng, chi phí tính theo lượt sử dụng, và dữ liệu giọng nói phải rời khỏi thiết bị người dùng.

Với một số lớp ứng dụng, cả ba ràng buộc đều là vấn đề. Thiết bị nhúng hoạt động ở nơi không có mạng ổn định. Ứng dụng y tế và tài chính chịu quy định nghiêm ngặt về dữ liệu cá nhân. Nghiên cứu học thuật cần khả năng can thiệp vào từng khâu xử lý, điều mà dịch vụ đóng không cho phép.

huggingface/speech-to-speech giải quyết lớp bài toán này. Nhưng giá trị của nó không nằm ở việc chạy được ngoại tuyến — nhiều dự án làm được điều đó. Giá trị nằm ở kiến trúc lắp ghép: mỗi khâu xử lý là một thành phần thay thế được qua tham số dòng lệnh, cho phép cùng một mã nguồn vận hành trên máy chủ có card đồ hoạ chuyên dụng lẫn trên máy tính cá nhân, với các mô hình hoàn toàn khác nhau.

2. Nguyên lý: dây chuyền nối tiếp và cái giá của nó

Bốn khâu xử lý

Micro → VAD → STT → LLM → TTS → Loa

VADVoice Activity Detection, phát hiện hoạt động giọng nói. Nhiệm vụ: xác định thời điểm người dùng bắt đầu và kết thúc lượt nói. Repo sử dụng Silero VAD phiên bản 5.

STTSpeech To Text, nhận dạng giọng nói thành văn bản.

LLMLarge Language Model, mô hình ngôn ngữ lớn sinh câu trả lời.

TTSText To Speech, tổng hợp văn bản thành âm thanh.

Vì sao là dây chuyền nối tiếp

Tồn tại hai hướng tiếp cận cho bài toán hội thoại giọng nói.

Hướng thứ nhất là mô hình đầu-cuối (end-to-end): một mô hình duy nhất nhận âm thanh vào và sinh âm thanh ra. Ưu điểm là giữ được thông tin phi ngôn từ — ngữ điệu, cảm xúc, tốc độ nói — vốn biến mất hoàn toàn khi âm thanh bị quy về văn bản. Nhược điểm là khó can thiệp, khó thay thế từng phần, và đòi hỏi tài nguyên lớn.

Hướng thứ hai là dây chuyền nối tiếp (cascade), tách bài toán thành các khâu độc lập. Đổi lại việc mất thông tin phi ngôn từ, ta được ba thứ: mỗi khâu dùng được mô hình chuyên biệt tốt nhất hiện có, mỗi khâu thay thế và gỡ lỗi độc lập, và tổng tài nguyên thấp hơn nhiều vì có thể chọn mô hình nhỏ cho từng khâu.

Repo chọn hướng thứ hai làm mặc định, đồng thời hỗ trợ hướng thứ nhất như một tuỳ chọn — bằng cách đặt khâu nhận dạng thành none để đưa âm thanh trực tiếp vào mô hình ngôn ngữ có khả năng nghe.

Bài toán độ trễ tích luỹ

Điểm yếu cố hữu của dây chuyền nối tiếp là độ trễ cộng dồn. Nếu bốn khâu chạy tuần tự, tổng thời gian chờ bằng tổng thời gian của từng khâu — con số này nhanh chóng vượt ngưỡng chấp nhận được của hội thoại tự nhiên, vốn nằm quanh mức vài trăm mili giây.

Repo xử lý bằng hai kỹ thuật.

Thứ nhất là xử lý theo luồng (streaming). Mô hình ngôn ngữ không đợi sinh xong toàn bộ câu trả lời rồi mới chuyển sang khâu tổng hợp giọng nói. Nó truyền ra từng cụm câu ngay khi có, và khâu tổng hợp bắt đầu đọc phần đầu trong khi phần sau vẫn đang được sinh. Các khâu do đó chạy chồng lấn thay vì chạy tuần tự.

Thứ hai là kiến trúc đa luồng. Mỗi khâu chạy trên một luồng riêng, nối với nhau bằng hàng đợi. Đây là điều kiện kỹ thuật để việc chồng lấn ở trên xảy ra được.

Kết quả là độ trễ cảm nhận được xấp xỉ thời gian của khâu chậm nhất cộng một khoảng nhỏ, thay vì tổng của cả bốn khâu.


3. Bài toán xác định kết thúc lượt nói

Đây là khâu quyết định phần lớn cảm giác tự nhiên của hội thoại, và cũng là nơi repo có đóng góp kỹ thuật đáng chú ý nhất.

Vì sao đây là bài toán khó

Khi con người nói chuyện, khoảng lặng giữa các từ và khoảng lặng kết thúc lượt nói không khác nhau nhiều về mặt tín hiệu. Người nghe phân biệt được nhờ hiểu nội dung và cảm nhận ngữ điệu — biết rằng "tôi muốn hỏi về..." là câu chưa trọn, còn "cảm ơn bạn" là đã xong.

Hệ thống chỉ dựa vào độ im lặng buộc phải chọn một ngưỡng thời gian. Ngưỡng ngắn thì cắt lời người dùng khi họ mới ngập ngừng. Ngưỡng dài thì mỗi lượt trả lời đều có độ trễ thừa. Không có giá trị nào tối ưu cho mọi tình huống.

Cách repo giải quyết

Repo tích hợp Smart Turn phiên bản 3.2, một mô hình riêng biệt xem xét nội dung văn bản và đặc trưng ngữ điệu của lượt nói để ước lượng xác suất người dùng đã nói xong.

Điểm tinh tế nằm ở cách phối hợp giữa hai tín hiệu. Hệ thống không đợi Smart Turn quyết định rồi mới hành động. Thay vào đó:

  1. Khi VAD báo hết lượt nói, các khâu phía sau bắt đầu xử lý ngay một cách suy đoán.
  2. Kết quả sinh ra chưa được phát cho người dùng mà bị giữ lại trong một khoảng chờ ngắn.
  3. Nếu người dùng nói tiếp trong khoảng đó, lượt nói cũ được mở lại như một phiên bản mới, phần âm thanh tích luỹ được gửi lại, và kết quả đã sinh bị huỷ trước khi tới tai người dùng.
  4. Nếu không có gì xảy ra, kết quả được phát ra.

Kỹ thuật này — thường gọi là speculative execution, thực thi suy đoán — đánh đổi một phần tài nguyên tính toán có thể bị bỏ phí để lấy độ trễ thấp mà không hy sinh độ chính xác. Đây là cùng một nguyên lý mà bộ xử lý hiện đại dùng khi dự đoán nhánh rẽ.

Các tham số điều chỉnh gồm ngưỡng quyết định, khoảng chờ cho câu đã trọn vẹn, và khoảng chờ cho câu còn dở dang.


4. Giao thức: tương thích OpenAI Realtime

Máy chủ mở điểm kết nối tại ws://localhost:8765/v1/realtime theo giao thức OpenAI Realtime.

Đây là quyết định thiết kế có giá trị thực tiễn cao nhất trong repo, vì nó biến dự án từ một công cụ độc lập thành một thành phần thay thế được trong hệ sinh thái sẵn có. Ứng dụng đã viết cho dịch vụ của OpenAI chuyển sang chạy local chỉ bằng cách đổi địa chỉ máy chủ:

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8765/v1",
    websocket_base_url="ws://localhost:8765/v1",
    api_key="not-needed",
)

with client.realtime.connect(model="local") as conn:
    for event in conn:
        print(event.type)

Repo cài đặt tập sự kiện cốt lõi của giao thức: nhận âm thanh vào, cập nhật cấu hình phiên, tạo và huỷ câu trả lời ở chiều vào; báo bắt đầu và kết thúc lời nói, bản chép trực tiếp, luồng âm thanh trả về và lời gọi công cụ ở chiều ra.

Hai phương thức truyền tải được hỗ trợ. WebSocket là đường truyền hai chiều đơn giản, âm thanh được mã hoá base64 và gửi thành từng khối nhỏ. WebRTC là giao thức chuyên dụng cho âm thanh thời gian thực của trình duyệt, có cơ chế chống mất gói và bù trễ tốt hơn, đổi lại quá trình thiết lập kết nối phức tạp hơn.

Chế độ máy chủ trung gian cho mô hình ngôn ngữ

Một tính năng ít gặp: khi bật, máy chủ đồng thời phơi mô hình ngôn ngữ mà nó đang dùng ra thành một điểm kết nối tương thích OpenAI thông thường.

Điều này cho phép ứng dụng khách chạy các tác vụ phụ — tóm tắt hội thoại, đặt tiêu đề, xử lý nền — song song với cuộc hội thoại giọng nói mà không làm gián đoạn nó. Không cần nạp thêm một bản sao mô hình thứ hai vào bộ nhớ.

Lưu ý an toàn: điểm kết nối này không có xác thực. Chỉ nên bật trong mạng tin cậy hoặc đặt sau một lớp kiểm soát truy cập.


5. Các thành phần thay thế được

Khâu Thành phần Nền tảng hỗ trợ
VAD Silero VAD v5 Mọi nền tảng
STT Parakeet TDT (mặc định) NVIDIA / CPU / Apple Silicon qua MLX
STT Whisper qua Transformers NVIDIA / CPU
STT Faster Whisper NVIDIA / CPU
STT Lightning Whisper MLX Apple Silicon
STT MLX Audio Whisper Apple Silicon
STT Paraformer NVIDIA / CPU
STT Điểm kết nối tương thích OpenAI Máy chủ bất kỳ
LLM API tương thích OpenAI Đám mây hoặc máy chủ tự dựng
LLM Transformers NVIDIA / CPU
LLM mlx-lm Apple Silicon
TTS Qwen3-TTS (mặc định) NVIDIA / Apple Silicon qua MLX
TTS Kokoro-82M NVIDIA / CPU / Apple Silicon
TTS Supertonic NVIDIA / CPU
TTS Pocket TTS NVIDIA / CPU
TTS ChatTTS NVIDIA / CPU
TTS MMS TTS NVIDIA / CPU

Chọn bằng ba tham số --stt, --llm_backend, --tts.

Ba chế độ khởi chạy

Lệnh Chức năng Trường hợp áp dụng
serve Chạy máy chủ Realtime Xây ứng dụng hoặc thiết bị kết nối vào
talk --url <địa chỉ> Ứng dụng micro/loa dòng lệnh Đã có máy chủ, cần giao tiếp ngay
local Gộp cả hai trong một tiến trình Thử nghiệm nhanh

6. Yêu cầu phần cứng

Hiểu về lượng tử hoá trước khi chọn cấu hình

Trọng số của mô hình ngôn ngữ mặc định được lưu ở dạng số thực 16 bit. Lượng tử hoá (quantization) là kỹ thuật giảm số bit biểu diễn mỗi trọng số — xuống 8 bit, 6 bit hoặc 4 bit — nhằm giảm dung lượng bộ nhớ và tăng tốc độ tính toán.

Đánh đổi là sai số biểu diễn tăng lên. Tuy nhiên với mô hình ngôn ngữ, mức suy giảm chất lượng ở 4 bit thường nhỏ hơn nhiều so với mức tiết kiệm bộ nhớ đạt được. Quy tắc ước lượng thực dụng:

Mức lượng tử Bộ nhớ cho mô hình 4 tỷ tham số Ghi chú
bf16 (16 bit) ~8 GB Chất lượng tham chiếu
8bit ~4 GB Khác biệt hầu như không đo được
4bit ~2.5 GB Đủ dùng cho hội thoại thông thường

Với hội thoại giọng nói, nơi câu trả lời ngắn và độ trễ quan trọng hơn chiều sâu lập luận, mức 4 bit là lựa chọn hợp lý cho phần lớn trường hợp.

Cần lưu ý thêm: bộ nhớ cho trọng số mô hình chỉ là một phần. Cần cộng thêm bộ đệm ngữ cảnh (KV cache) — vùng nhớ lưu trạng thái của cuộc hội thoại, tỉ lệ thuận với độ dài ngữ cảnh cấu hình — cùng với bộ nhớ cho các khâu nhận dạng và tổng hợp giọng nói.

Cấu hình đề xuất theo kịch bản

Kịch bản A — mô hình ngôn ngữ đặt trên đám mây, nhận dạng và tổng hợp giọng nói chạy local

Hạng mục Yêu cầu tối thiểu Đề xuất
Bộ nhớ 8 GB 8 GB
Bộ nhớ dành cho dây chuyền ~2–3 GB
Xử lý đồ hoạ Không bắt buộc Có tăng tốc thì tốt hơn
Kết nối mạng Bắt buộc, ổn định

Phù hợp khi ưu tiên chất lượng câu trả lời và độ trễ thấp, chấp nhận việc nội dung văn bản được gửi lên dịch vụ bên ngoài. Giọng nói vẫn được xử lý hoàn toàn tại máy.

Kịch bản B — toàn bộ dây chuyền chạy local

Hạng mục Yêu cầu tối thiểu Đề xuất
Bộ nhớ 16 GB 24 GB trở lên
Bộ nhớ dành cho dây chuyền ~5 GB (mô hình 4B ở mức 4 bit)
Xử lý đồ hoạ Apple Silicon, hoặc NVIDIA từ 8 GB VRAM
Dung lượng ổ đĩa 15 GB trống cho các mô hình 30 GB
Kết nối mạng Chỉ cần cho lần cài đặt đầu

Ở mức 16 GB, cần chỉ định tường minh mô hình bản 4 bit. Tham số cấu hình sẵn cho macOS mặc định dùng bản 16 bit, vốn phù hợp với máy từ 32 GB trở lên.

Kịch bản C — mô hình nghe âm thanh trực tiếp, bỏ khâu nhận dạng

Hạng mục Yêu cầu tối thiểu Đề xuất
Bộ nhớ 24 GB 32 GB
Xử lý đồ hoạ Apple Silicon, hoặc NVIDIA từ 16 GB VRAM
Dung lượng ổ đĩa 20 GB trống 40 GB

Kịch bản này giữ được ngữ điệu và cảm xúc trong giọng nói, nhưng yêu cầu tài nguyên cao hơn hẳn do mô hình đa phương thức có kích thước lớn.

Về triển khai bằng container

Tệp cấu hình Docker đi kèm repo yêu cầu card đồ hoạ NVIDIA cùng bộ công cụ container tương ứng. Trên máy Mac, container không truy cập được bộ tăng tốc đồ hoạ Metal và cũng không truy cập được thiết bị âm thanh của máy chủ, nên hướng đóng gói này chỉ áp dụng cho môi trường Linux có GPU chuyên dụng. Trên Apple Silicon, cài đặt trực tiếp là con đường duy nhất.


7. Cài đặt và chạy thử

Cài đặt

git clone https://github.com/huggingface/speech-to-speech.git
cd speech-to-speech
uv sync

uv là công cụ quản lý gói và môi trường Python. Đặc điểm đáng chú ý: nó tự tải phiên bản Python phù hợp vào môi trường ảo riêng của dự án. Repo yêu cầu Python từ 3.10, nhưng nếu hệ thống chỉ có sẵn phiên bản cũ hơn thì vẫn cài đặt được bình thường, không cần công cụ quản lý phiên bản riêng và không can thiệp vào Python của hệ điều hành — điều quan trọng trên macOS, nơi Python hệ thống được các thành phần khác phụ thuộc vào.

Môi trường sau khi cài chiếm khoảng 1.6 GB.

Chạy cấu hình local

uv run speech-to-speech local --mac-optimal-settings \
    --model_name mlx-community/Qwen3-4B-Instruct-2507-4bit

Tham số --mac-optimal-settings thiết lập sẵn tổ hợp phù hợp cho Apple Silicon: Parakeet TDT cho nhận dạng, mlx-lm cho mô hình ngôn ngữ, Qwen3-TTS cho tổng hợp, và định tuyến toàn bộ qua bộ tăng tốc Metal.

Lần chạy đầu tải về các mô hình cần thiết. Nên đăng nhập Hugging Face trước bằng hf auth login — yêu cầu không xác thực bị giới hạn tốc độ đáng kể, và với các tệp trọng số cỡ vài gigabyte, khác biệt là rõ rệt.

Giao diện web

Repo kèm sẵn ứng dụng web trong thư mục demo/, cung cấp lịch sử hội thoại, phát lại đoạn ghi âm của người dùng, chọn giọng đọc, chỉnh câu lệnh hệ thống và chọn thiết bị âm thanh.

npm ci --prefix demo

# Cửa sổ 1
uv run speech-to-speech serve --mac-optimal-settings \
    --model_name mlx-community/Qwen3-4B-Instruct-2507-4bit

# Cửa sổ 2
SPEECH_TO_SPEECH_URL="ws://localhost:8765/v1/realtime" \
uv run --with-requirements demo/requirements.txt \
    uvicorn --app-dir demo server:app --port 7860

Truy cập http://localhost:7860.

Một ràng buộc của trình duyệt cần biết: quyền truy cập micro chỉ được cấp trên localhost hoặc kết nối đã mã hoá. Địa chỉ mạng nội bộ dạng http://192.168.x.y sẽ bị từ chối. Đây là chính sách bảo mật của trình duyệt, không phải giới hạn của repo.

8. Tuỳ biến cho tiếng Việt

Danh sách ngôn ngữ được quy định ở tầng mã nguồn

Đây là điểm cần nắm trước tiên, vì nó không hiển nhiên.

Mỗi thành phần nhận dạng và tổng hợp giọng nói khai báo một danh sách mã ngôn ngữ trong mã nguồn của nó. Danh sách này không nhất thiết trùng với năng lực của mô hình bên dưới. Chẳng hạn mô hình Whisper bản large-v3 nhận dạng được gần một trăm ngôn ngữ trong đó có tiếng Việt, nhưng thành phần tích hợp Whisper qua Transformers trong repo chỉ khai báo mười hai mã.

Hệ quả thực tiễn: khả năng hỗ trợ một ngôn ngữ phải tra theo thành phần cụ thể, không thể suy từ tên mô hình.

Nhận dạng giọng nói:

Thành phần Số ngôn ngữ Tiếng Việt
Parakeet TDT (mặc định) 25, tập trung châu Âu Không
Whisper / Whisper MLX / MLX Audio Whisper 12 Không
Faster Whisper 99
Paraformer Tuỳ mô hình, mặc định thiên tiếng Trung Không

Tổng hợp giọng nói:

Thành phần Số ngôn ngữ Tiếng Việt
Qwen3-TTS (mặc định) 10 Không
Supertonic 32
Kokoro 9 Không
MMS TTS Nhiều
ChatTTS 2 (Anh, Trung) Không

Giao của hai bảng cho tiếng Việt còn lại tổ hợp chính: Faster Whisper cho đầu vào và Supertonic cho đầu ra.

Điểm cần nhấn mạnh: cả hai thành phần mặc định đều không hỗ trợ tiếng Việt. Cấu hình chạy ngay sau khi cài là một hệ thống tiếng Anh.

Cấu hình

uv sync --extra faster-whisper --extra supertonic

uv run speech-to-speech local \
    --stt faster-whisper \
    --faster_whisper_stt_gen_language vi \
    --faster_whisper_stt_device cpu \
    --faster_whisper_stt_compute_type int8 \
    --tts supertonic \
    --supertonic_tts_lang vi

Quy ước đặt tên tham số. Mỗi thành phần có một tiền tố riêng cho nhóm tham số của nó. Ngôn ngữ của Faster Whisper đặt qua --faster_whisper_stt_gen_language; tham số --language không dùng chung mà thuộc riêng thành phần Whisper của Transformers. Truyền nhầm thì chương trình vẫn chạy nhưng bỏ qua giá trị đó kèm một dòng cảnh báo. Cách tra chính xác là chạy lệnh với -h sau khi đã chỉ định các thành phần, vì danh sách tham số hiển thị phụ thuộc vào tổ hợp đang chọn.

Về hiệu năng. Faster Whisper xây trên thư viện suy luận CTranslate2, thư viện này chưa hỗ trợ bộ tăng tốc Metal, nên trên nền tảng Apple nó chạy bằng CPU. Đây trở thành khâu chậm nhất trong dây chuyền. Nếu độ trễ vượt ngưỡng chấp nhận được, hạ kích thước mô hình từ large-v3 xuống medium hoặc small đổi lấy tốc độ — với tiếng Việt trong hội thoại thông thường, mô hình cỡ vừa thường đã đủ.


9. Tuỳ biến: đầu vào và đầu ra khác ngôn ngữ

Một tình huống ứng dụng thực tế: người dùng nói tiếng Việt nhưng muốn nghe câu trả lời bằng tiếng Nhật để luyện nghe, hoặc ngược lại.

Yêu cầu kỹ thuật: khâu tổng hợp giọng nói phải bỏ qua ngôn ngữ mà khâu nhận dạng phát hiện được và đọc theo một ngôn ngữ cố định.

Hai thành phần tổng hợp xử lý theo hai triết lý trái ngược:

  • Qwen3-TTS bỏ qua mã ngôn ngữ do khâu trước truyền sang, luôn dùng giá trị đặt trong tham số cấu hình.
  • Supertonic làm ngược lại: ưu tiên mã ngôn ngữ do khâu trước truyền sang, chỉ quay về giá trị cấu hình khi ngôn ngữ đó không được hỗ trợ.

Cả hai đều hợp lý cho những mục đích khác nhau. Từ đó suy ra cách chọn:

Nhu cầu Thành phần phù hợp Lý do
Nói và trả lời cùng ngôn ngữ Supertonic Tự bám theo đầu vào, hỗ trợ tiếng Việt
Nói và trả lời khác ngôn ngữ Qwen3-TTS Cho phép cố định đầu ra độc lập với đầu vào
uv run speech-to-speech serve \
    --stt faster-whisper \
    --faster_whisper_stt_gen_language vi \
    --faster_whisper_stt_device cpu \
    --faster_whisper_stt_compute_type int8 \
    --tts qwen3 \
    --qwen3_tts_language ja \
    --qwen3_tts_speaker ono_anna

Câu lệnh hệ thống nên viết bằng ngôn ngữ đích

Một quan sát thực nghiệm có cơ sở lý thuyết: với mô hình ngôn ngữ cỡ vài tỷ tham số, việc hướng dẫn bằng ngôn ngữ A rằng "hãy trả lời bằng ngôn ngữ B" thường không đủ ổn định. Mô hình có xu hướng bị ngôn ngữ chiếm ưu thế trong ngữ cảnh — ở đây là ngôn ngữ của câu hỏi — kéo theo.

Viết câu lệnh trực tiếp bằng ngôn ngữ đích, kèm ràng buộc phủ định tường minh, cho kết quả ổn định hơn đáng kể, vì nó đưa thêm tín hiệu ngôn ngữ đích vào ngữ cảnh:

ユーザーはベトナム語で話します。あなたは必ず日本語だけで答えてください。
ベトナム語や英語では絶対に答えないこと。短く、自然な話し言葉で。

(Người dùng nói tiếng Việt. Bạn phải luôn trả lời chỉ bằng tiếng Nhật. Tuyệt đối không trả lời bằng tiếng Việt hay tiếng Anh. Trả lời ngắn gọn, bằng văn nói tự nhiên.)

Mô hình càng lớn thì hiệu ứng này càng ít quan trọng.


10. Tuỳ biến: giọng đọc

Qwen3-TTS cung cấp chín giọng đặt sẵn. Danh sách chính xác nằm trong tệp cấu hình của mô hình, ở mục định danh người nói:

serena, vivian, uncle_fu, ryan, aiden, ono_anna, sohee, eric, dylan

Cùng mười một mã ngôn ngữ:

auto, chinese, english, german, italian, portuguese,
spanish, japanese, korean, french, russian

Giọng đọc và ngôn ngữ là hai tham số độc lập. Hệ thống chỉ kiểm tra tên giọng có tồn tại hay không, không ràng buộc giọng với ngôn ngữ. Mọi cặp giá trị đều hợp lệ về mặt kỹ thuật, nhưng chất lượng phát âm thì khác nhau — mỗi giọng được huấn luyện chủ yếu trên một ngôn ngữ nhất định, và khi đọc ngôn ngữ khác có thể mang theo đặc trưng phát âm của ngôn ngữ gốc.

Không có cách nào xác định từ mã nguồn xem giọng nào phù hợp nhất với một ngôn ngữ cụ thể. Đây là câu hỏi về chất lượng âm thanh, chỉ đánh giá được bằng cách nghe. Phương pháp thực tế là sinh cùng một câu bằng tất cả các giọng rồi so sánh trực tiếp.

Trên giao diện web, việc đổi giọng có hiệu lực ngay mà không cần khởi động lại máy chủ.


11. Tuỳ biến mã nguồn: cố định câu lệnh hệ thống

Thứ tự ưu tiên của cấu hình

Câu lệnh hệ thống — đoạn văn bản định hướng hành vi của mô hình — có thể được đặt ở ba nơi: tham số dòng lệnh khi khởi động máy chủ, biến môi trường của ứng dụng web, và ô nhập trong giao diện.

Theo thiết kế của giao thức Realtime, giá trị do ứng dụng khách gửi lên có mức ưu tiên cao nhất. Khi trình duyệt kết nối, nó gửi kèm cấu hình phiên trong đó có câu lệnh hệ thống, và máy chủ dùng giá trị này cho mọi lượt của phiên đó.

Đây là hành vi đúng đắn: giao thức được thiết kế để ứng dụng khách kiểm soát hành vi của phiên làm việc. Nhưng khi tự triển khai một hệ thống có hành vi cố định — chẳng hạn luôn trả lời bằng một ngôn ngữ nhất định — ta cần một cách ấn định giá trị này từ phía máy chủ.

Bản demo có sẵn câu lệnh mặc định bằng tiếng Anh trong mã nguồn phía trình duyệt. Nếu chỉ đặt câu lệnh qua tham số dòng lệnh, giá trị mặc định này sẽ được dùng thay thế.

Cách bổ sung

Bản demo đã có sẵn khuôn mẫu cho tình huống tương tự ở một tính năng khác: biến môi trường STARTUP_GREETING được đọc ở phía máy chủ, đưa vào điểm truy vấn cấu hình, rồi phía trình duyệt đọc về sử dụng. Áp dụng cùng khuôn mẫu cho câu lệnh hệ thống là cách bổ sung nhất quán với thiết kế sẵn có.

Phía máy chủ, trong demo/server.py — thêm biến môi trường và đưa vào cấu hình trả về:

INSTRUCTIONS = os.environ.get("INSTRUCTIONS", "").strip()
"startupGreeting": STARTUP_GREETING,
"instructions": INSTRUCTIONS,

Phía trình duyệt, trong demo/main.js — khi nhận cấu hình, nếu máy chủ có chỉ định thì dùng giá trị đó:

const pinnedInstructions = typeof json.instructions === "string"
  ? json.instructions.trim()
  : "";
if (pinnedInstructions) {
  settings.instructions = pinnedInstructions;
  localStorage.setItem(STORAGE_KEYS.instructions, pinnedInstructions);
  if (inputInstructions) inputInstructions.value = pinnedInstructions;
}

Đoạn mã trên có một chi tiết cần giải thích rõ.

localStorage là vùng lưu trữ local của trình duyệt, dùng để ghi nhớ tuỳ chọn của người dùng giữa các lần truy cập. Ở đây nó lưu câu lệnh hệ thống mà người dùng đã nhập lần trước.

Trong luồng thông thường, giá trị đã lưu trong localStorage được đọc ra và sử dụng. Nhưng khi máy chủ chỉ định một câu lệnh qua biến môi trường, ta muốn giá trị của máy chủ được dùng thay cho giá trị đã lưu, đồng thời ghi đè lên giá trị đó.

Lý do của lựa chọn này: sau vài lần thử nghiệm, trình duyệt gần như chắc chắn đã lưu lại một câu lệnh từ lần trước. Nếu tiếp tục ưu tiên giá trị đã lưu, phần cấu hình ở máy chủ sẽ không có hiệu lực, và biểu hiện bên ngoài không phân biệt được với trường hợp chưa cấu hình gì.

Cách xử lý này nhất quán với cách bản demo đối xử với địa chỉ máy chủ: khi được ấn định qua biến môi trường, giá trị đó hiển thị ở dạng chỉ đọc và người dùng không sửa được.

Sử dụng:

INSTRUCTIONS="ユーザーはベトナム語で話します。必ず日本語だけで答えてください。" \
SPEECH_TO_SPEECH_URL="ws://localhost:8765/v1/realtime" \
uv run --with-requirements demo/requirements.txt \
    uvicorn --app-dir demo server:app --port 7860

Kiểm chứng:

curl -s http://localhost:7860/api/config | jq '.instructions'

Sau khi sửa mã phía trình duyệt, cần tải lại trang ở chế độ bỏ qua bộ nhớ đệm, nếu không trình duyệt tiếp tục dùng bản mã cũ.

12. Đọc hiểu các chỉ số hiệu năng

Khi đánh giá một dây chuyền giọng nói, hai chỉ số sau quan trọng hơn tốc độ thô.

Độ trễ tới âm thanh đầu tiên (time to first audio) — khoảng thời gian từ lúc bắt đầu tổng hợp đến lúc mẫu âm thanh đầu tiên sẵn sàng phát. Chỉ số này quyết định cảm giác đáp ứng của hệ thống. Người dùng cảm nhận độ trễ ở thời điểm bắt đầu nghe thấy tiếng, không phải ở thời điểm câu trả lời hoàn tất.

Tỉ lệ thời gian thực (real-time factor) — tỉ số giữa độ dài âm thanh sinh ra và thời gian tính toán để sinh ra nó. Giá trị lớn hơn 1.0 nghĩa là hệ thống sinh nhanh hơn tốc độ phát, do đó không bị hụt tiếng giữa chừng. Giá trị dưới 1.0 nghĩa là âm thanh sẽ bị ngắt quãng.

Trên một máy Apple Silicon 16 GB với tổ hợp mô hình 4 tỷ tham số ở mức 4 bit, các chỉ số đo được nằm quanh mức dưới nửa giây cho độ trễ đầu tiên và trên 1.5 cho tỉ lệ thời gian thực — đủ để hội thoại diễn ra liên tục.

Khâu quyết định độ trễ tổng thể là nhận dạng giọng nói, đặc biệt khi buộc phải chạy trên CPU. Đây là điểm cần cân nhắc đầu tiên khi tối ưu.


13. Hướng phát triển

Cải tiến trong tầm với

Mở rộng danh sách ngôn ngữ đã khai báo. Khoảng cách giữa năng lực của mô hình Whisper và số mã ngôn ngữ được khai báo trong một số thành phần là đáng kể. Thu hẹp khoảng cách này mở khoá nhiều ngôn ngữ mà không cần thay đổi mô hình.

Kiểm tra tính tương thích ngôn ngữ khi khởi động. Hiện nếu chọn một thành phần không hỗ trợ ngôn ngữ mong muốn, hệ thống vẫn vận hành bình thường và chỉ cho kết quả sai. Một bước kiểm tra lúc khởi động, đối chiếu ngôn ngữ được yêu cầu với danh sách hỗ trợ của tổ hợp đang chọn, sẽ phát hiện sớm loại cấu hình sai này.

Thống nhất ngữ nghĩa xử lý ngôn ngữ giữa các thành phần tổng hợp. Việc một thành phần bám theo ngôn ngữ đầu vào còn thành phần khác bỏ qua nó là hai hành vi đều hợp lý, nhưng hiện là đặc tính ngầm. Nâng lên thành một tham số tường minh sẽ giúp người dùng chọn có ý thức thay vì phát hiện qua thử nghiệm.

Mở rộng năng lực

Hỗ trợ tăng tốc phần cứng cho nhận dạng đa ngôn ngữ. Hiện tồn tại một sự đánh đổi: hoặc chọn thành phần chạy trên bộ tăng tốc nhưng ít ngôn ngữ, hoặc chọn thành phần nhiều ngôn ngữ nhưng chạy CPU. Một cài đặt Whisper trên nền tảng tăng tốc mà giữ nguyên độ phủ ngôn ngữ sẽ loại bỏ đánh đổi này.

Bổ sung lựa chọn tổng hợp giọng nói cho các ngôn ngữ chưa được phục vụ đầy đủ. Tiếng Việt hiện có hai lựa chọn, con số khiêm tốn so với quy mô người dùng.

Giảm yêu cầu tài nguyên cho mô hình nghe âm thanh trực tiếp. Hướng bỏ khâu nhận dạng, đưa âm thanh thẳng vào mô hình ngôn ngữ, có ưu thế lý thuyết rõ ràng: giữ được thông tin phi ngôn từ và rút ngắn dây chuyền. Rào cản hiện tại thuần tuý là tài nguyên. Các kỹ thuật nén mô hình tiến bộ sẽ dần đưa hướng này xuống tầm máy tính cá nhân phổ thông.

Bài toán nghiên cứu

Nhận dạng khi trộn ngôn ngữ trong cùng phát ngôn. Hiện tượng chuyển mã (code-switching) rất phổ biến ở người nói song ngữ — người Việt bàn về công nghệ chèn tiếng Anh liên tục. Các hệ thống hiện tại xử lý ở mức chọn một ngôn ngữ cho cả lượt nói. Nhận dạng chính xác khi hai ngôn ngữ đan xen trong một câu vẫn là bài toán mở.

Bảo toàn thông tin phi ngôn từ qua dây chuyền. Trong kiến trúc nối tiếp, ngữ điệu và cảm xúc của người nói bị loại bỏ ở khâu nhận dạng và không thể khôi phục ở khâu tổng hợp. Việc truyền các đặc trưng này song song với văn bản, để khâu tổng hợp tái tạo ngữ điệu tương ứng, là hướng có giá trị ứng dụng cao — đặc biệt với dịch thuật hội thoại.

Đánh giá tự động trên không gian tổ hợp. Với số lượng thành phần hiện có, số tổ hợp khả dĩ vượt xa khả năng kiểm thử thủ công. Một bộ khung đánh giá tự động, chạy qua các tổ hợp và xuất ra ma trận năng lực theo ngôn ngữ kèm chỉ số hiệu năng, sẽ có giá trị hơn tài liệu viết tay và không bị lạc hậu theo thời gian.


14. Kết luận

huggingface/speech-to-speech phù hợp với ba nhóm nhu cầu: triển khai trợ lý giọng nói trên thiết bị không có kết nối ổn định, xây dựng hệ thống có ràng buộc về dữ liệu cá nhân, và nghiên cứu thử nghiệm nhiều tổ hợp mô hình mà không phải viết lại hạ tầng.

Hai quyết định thiết kế đáng ghi nhận. Thứ nhất là kiến trúc bốn khâu thay thế được từng phần, cho phép cùng mã nguồn vận hành trên dải phần cứng rất rộng. Thứ hai là việc tuân thủ giao thức OpenAI Realtime, nên dự án cắm được vào các công cụ và thư viện có sẵn thay vì là một hệ thống đóng, buộc client phải viết lớp tích hợp riêng.