Embedding & vector store
Embedding biến văn bản thành tọa độ trong một không gian nhiều chiều, nơi “gần nghĩa” trở thành “gần nhau”. Vector store là nơi tìm những điểm gần nhau đó thật nhanh.
Embedding là gì
Mô hình embedding nhận một đoạn văn và trả về một vector — thường vài trăm tới vài nghìn số thực. Mô hình được huấn luyện sao cho các đoạn có ý nghĩa tương tự nằm gần nhau, kể cả khi không dùng chung từ nào:
| Câu | Gần với |
|---|---|
| “Tôi muốn làm việc ở nhà vài hôm” | “Quy định làm việc từ xa” |
| “Laptop bị hỏng thì báo ai?” | “Quy trình hỗ trợ sự cố thiết bị” |
| “Được nghỉ mấy ngày một năm?” | “Chế độ nghỉ phép năm” |
Đây là điểm mạnh so với tìm kiếm theo từ khoá thuần tuý — và cũng là lý do phòng thí nghiệm của khoá (dùng TF-IDF, dựa trên từ chung) sẽ bỏ lỡ những cặp như trên.
Đo độ gần: cosine similarity
Độ gần phổ biến nhất là cosine của góc giữa hai vector:
Giá trị gần 1 nghĩa là cùng hướng (rất giống), gần 0 là không liên quan. Ví dụ với vector 3 chiều, đủ nhỏ để kiểm bằng tay:
câu hỏi q = [0.9, 0.1, 0.3]
đoạn A a = [0.8, 0.2, 0.4] (nói về nghỉ phép)
đoạn B b = [0.1, 0.9, 0.2] (nói về thiết bị)
q · a = 0.72 + 0.02 + 0.12 = 0.86 ‖q‖ ≈ 0.9539 ‖a‖ ≈ 0.9165
cos(q, a) = 0.86 / (0.9539 × 0.9165) = 0.9836
q · b = 0.09 + 0.09 + 0.06 = 0.24 ‖b‖ ≈ 0.9274
cos(q, b) = 0.24 / (0.9539 × 0.9274) = 0.2713
Đoạn A được xếp trên đoạn B, hơn 3,6 lần. Nhiều hệ thống chuẩn hoá vector về độ dài 1; khi đó cosine bằng đúng tích vô hướng và tính nhanh hơn.
Chọn mô hình embedding
- Ngôn ngữ: với tiếng Việt, chọn mô hình đa ngôn ngữ và kiểm tra trên chính câu hỏi của bạn — chất lượng giữa các mô hình chênh lệch rõ và không đi theo điểm benchmark tiếng Anh.
- Cùng một mô hình cho index và câu hỏi. Vector từ hai mô hình khác nhau không so sánh được. Đổi mô hình = embed lại toàn bộ kho.
- Số chiều: nhiều chiều thường chính xác hơn nhưng tốn bộ nhớ và chậm hơn. Một số mô hình cho phép rút gọn số chiều.
- Giới hạn token đầu vào: chunk dài hơn giới hạn sẽ bị cắt, đôi khi không báo gì.
- Tiền tố truy vấn/tài liệu: một số mô hình yêu cầu thêm tiền tố khác nhau cho câu hỏi và cho đoạn văn — đọc kỹ tài liệu mô hình trước khi lập chỉ mục cả triệu đoạn.
Vector store và chỉ mục ANN
Với vài nghìn đoạn, có thể so câu hỏi với từng vector (brute force). Với hàng triệu đoạn, cần chỉ mục ANN — Approximate Nearest Neighbor: chấp nhận đôi khi bỏ sót một láng giềng để đổi lấy tốc độ nhanh hơn nhiều bậc.
| Chỉ mục | Ý tưởng | Đánh đổi |
|---|---|---|
| HNSW | Đồ thị nhiều tầng, đi từ tầng thưa xuống tầng dày để tới gần câu hỏi | Nhanh, recall cao; tốn RAM, build chậm hơn |
| IVF | Chia vector thành cụm, chỉ tìm trong vài cụm gần nhất | Nhẹ hơn; cần huấn luyện cụm, recall phụ thuộc số cụm được dò |
| PQ (lượng tử hoá) | Nén vector thành mã ngắn | Tiết kiệm bộ nhớ lớn; giảm độ chính xác |
Lựa chọn lưu trữ thường gặp: pgvector (nếu đã dùng PostgreSQL), Qdrant, Milvus, Weaviate, Elasticsearch/OpenSearch (mạnh cả tìm kiếm từ khoá), hoặc thư viện FAISS khi tự quản lý.
Ví dụ với PostgreSQL + pgvector
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
doc_id text NOT NULL,
section text,
content text NOT NULL,
department text,
updated_at date,
embedding vector(1024) -- đúng số chiều của mô hình
);
-- Chỉ mục HNSW theo khoảng cách cosine
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);
-- Top-5 đoạn gần câu hỏi nhất, chỉ trong tài liệu HR
-- <=> là KHOẢNG CÁCH cosine, nên độ tương đồng = 1 - khoảng cách
SELECT doc_id, section, content,
1 - (embedding <=> $1) AS similarity
FROM chunks
WHERE department = 'HR'
ORDER BY embedding <=> $1
LIMIT 5;
department = 'HR', chỉ mục HNSW trên vector_cosine_ops, chạy đúng câu truy vấn ở trên với LIMIT 5.
Ở mức mặc định hnsw.ef_search = 40, câu lệnh trả về 3 dòng chứ không phải 5. EXPLAIN ANALYZE nói rõ vì sao:
Index Scan using chunks_hnsw (actual rows=3) kèm Rows Removed by Filter: 388 — chỉ mục đưa lên 391 ứng viên chỉ dựa trên khoảng cách, rồi mệnh đề WHERE loại hết chỉ còn ba.
Nâng lên SET hnsw.ef_search = 100 thì đủ 5 dòng, quét tuyệt đối cũng 5 dòng. Không có cảnh báo nào: câu lệnh vẫn báo thành công với kết quả thiếu.
Vậy nên: đếm số dòng thực nhận, tăng tham số dò tìm, hoặc dùng vector store lọc trước khi tìm trong chỉ mục thay vì lọc sau.
Tự kiểm tra
Đổi sang mô hình embedding mới tốt hơn thì phải làm gì với dữ liệu cũ?
Embed lại toàn bộ kho bằng mô hình mới (và cập nhật số chiều cột vector). Không được trộn vector của hai mô hình trong cùng một chỉ mục.Vì sao gọi là “approximate” nearest neighbor?
Chỉ mục đánh đổi: không đảm bảo trả về đúng những láng giềng gần nhất tuyệt đối, đổi lấy tốc độ. Mức đánh đổi điều chỉnh bằng tham số — với HNSW của pgvector làhnsw.ef_search, số ứng viên giữ lại khi đi xuống đồ thị.