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âuGầ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:

cos(a, b) = (a · b) / (‖a‖ × ‖b‖)

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

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ỏiNhanh, recall cao; tốn RAM, build chậm hơn
IVFChia vector thành cụm, chỉ tìm trong vài cụm gần nhấtNhẹ 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ắnTiế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ý.

Tiêu chí thực tế Hỗ trợ lọc metadata trong cùng truy vấn, hybrid search, cập nhật/xoá từng đoạn, sao lưu, và đội của bạn có vận hành nổi hay không. Với dưới vài triệu đoạn, PostgreSQL + pgvector thường là lựa chọn ít rắc rối nhất.

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;
Điều kiện lọc chặt có thể lặng lẽ trả về ít dòng hơn bạn yêu cầu Đo trên PostgreSQL 16.15 với pgvector 0.8.6: 50.000 đoạn 128 chiều, trong đó 500 đoạn (1%) mang 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ị.
Câu hỏi chứa mã sản phẩm “SKU-48213”. Embedding có tìm tốt không? Thường không: mã, số hiệu, tên riêng hiếm là điểm yếu của embedding. Đây là lý do cần kết hợp tìm kiếm từ khoá (BM25) — xem Truy xuất.