Tầng 5 — ANN & hạ tầng vector
Bản cụ thể hoá. Mục này nói về cơ chế chung. Nếu bạn đang thật sự chạy Qdrant, toàn bộ những thứ dưới đây có một bản chi tiết theo đúng cài đặt của nó ở Phần 7 — Qdrant: segment và optimizer (7.4, 7.6), tham số HNSW thật (7.7), bẫy filter (7.8), và mô hình bộ nhớ (7.10).
Recall là thứ đánh đổi được, không phải thứ cho không
Phần tiêu đề “Recall là thứ đánh đổi được, không phải thứ cho không”Brute-force cho recall 1.0 với chi phí O(N). ANN đánh đổi recall lấy tốc độ. Recall của ANN là một tham số bạn chọn, và nhiều hệ production đang chạy ở recall 0.85 mà không ai biết.
HNSW — bên trong
Phần tiêu đề “HNSW — bên trong”
Cơ chế bên trong — vì sao greedy routing trên đồ thị lại nhanh (Kleinberg, NSW), năm thuật toán của paper gốc viết ra thành pseudo-code, tính tầng của một điểm bằng tay, vì sao phải cắt tỉa bằng heuristic, chỗ HNSW đau khi xoá, và Vamana/DiskANN: 6.2 — ANN đồ thị: từ small world tới HNSW. Mục này chỉ nói xoay núm nào.
Đồ thị nhiều tầng. Tầng trên thưa (bước dài, đi xa nhanh), tầng dưới dày (bước ngắn, tinh chỉnh). Tìm kiếm = greedy descent: vào từ tầng trên, đi đến điểm gần nhất, tụt xuống tầng dưới, lặp lại.
| Tham số | Ý nghĩa | Đánh đổi |
|---|---|---|
M | số kết nối mỗi node | cao → recall tốt, RAM nhiều hơn (~M × 8 byte/vector) |
efConstruction | độ rộng tìm kiếm khi xây | cao → đồ thị chất lượng hơn, build chậm hơn. Chỉ trả giá một lần. |
efSearch | độ rộng tìm kiếm khi truy vấn | đây là núm điều chỉnh recall/latency tại runtime |
→ Nếu bạn chỉ nhớ một điều: efSearch là núm bạn xoay khi cần recall cao hơn, và
nó không cần build lại index.
IVF & quantization — khi RAM là ràng buộc
Phần tiêu đề “IVF & quantization — khi RAM là ràng buộc”IVF: phân cụm vector thành nlist cụm, truy vấn chỉ quét nprobe cụm gần nhất.
Đây là cách giảm số phép so sánh. (Cơ chế đầy đủ — vì sao nlist ≈ 4√N, vì sao IVF cần
train trước nên không dựng được từ index rỗng, và bảng so IVF vs HNSW theo mười một trục:
6.2 §1.1–1.3. Bản thân
bước phân cụm — thuật toán Lloyd, chi phí train, khởi tạo, spherical cho cosine, và
centroid lệch dần: 6.2 §1.1 — k-means.)
Quantization là cách giảm kích thước mỗi phép
so sánh — hai trục độc lập, thường dùng chung.
Cơ chế bên trong — scalar / PQ / binary hoạt động thế nào, tính bằng tay, vì sao binary cần vector nhiều chiều: 6.1 — Quantization cho vector. Mục này chỉ nói chọn cái nào và cấu hình ra sao.
Bốn họ quantization
Phần tiêu đề “Bốn họ quantization”Scalar quantization (SQ) — mỗi chiều float32 → uint8. Nén 4×. Qdrant ghi sai số “thường dưới 1%”, và so sánh int8 được SIMD tăng tốc nên còn nhanh hơn float32. Đây là mặc định an toàn, và là lựa chọn duy nhất còn tốt với khoảng cách Manhattan (L1).
Product quantization (PQ) — chia vector thành m đoạn, mỗi đoạn quantization về 1 byte codebook (k-means). 1536 chiều × 4 byte = 6 KB → m=96 → 96 byte, nén ~64×. Nén sâu nhất, nhưng phép tính khoảng cách không SIMD-friendly nên chậm hơn scalar. Chỉ dùng khi bộ nhớ là ràng buộc số một.
Binary quantization (BQ) — 1 bit/chiều, nén 32×, so sánh bằng Hamming distance (rất
nhanh — Qdrant báo tới ~40× nhanh hơn vector gốc). Điều kiện: vector phải đủ nhiều chiều
và phân bố các chiều phải cân quanh 0. Số Qdrant công bố: text-embedding-ada-002 (1536
chiều) đạt recall@100 = 0.98 với oversampling 4×; Cohere embed-english-v2.0 (4096 chiều)
đạt recall@50 = 0.98 với oversampling 2×. Ở 384 chiều thì BQ sập. Từ v1.5 có thêm biến thể
2-bit (nén 16×) và 1.5-bit (nén 24×) để bớt mất mát.
TurboQuant (TQ) — Qdrant v1.18+, dựa trên paper ICLR 2026. Đây là thứ mới đáng chú ý nhất trong nhóm này. Ý tưởng một dòng: quay vector bằng một phép quay trực giao ngẫu nhiên trước khi nén, để thông tin dàn đều ra các chiều thay vì dồn vào vài chiều — cùng một số bit nhưng méo ít hơn. 4-bit (mặc định) nén 8×, 2-bit 16×, 1.5-bit 24×, 1-bit 32×. Mặc định bất đối xứng: query giữ full precision, chỉ vector lưu trong index bị nén.
Trên benchmark 10 dataset của Qdrant (arxiv-384, dbpedia-ada/gemini/openai-3, wikipedia-cohere, H&M 2048 chiều, LAION CLIP…):
- TQ 4-bit đạt recall ngang scalar (lệch 1–2 pp, có dataset còn cao hơn tới 4.6 pp) mà nén gấp đôi (8× vs 4×).
- Ở cùng mức nén, TQ hơn binary 9–24 pp tại 16× và 9–21 pp tại 32×.
Hỗ trợ cosine / dot / L2 với SIMD; L1 chạy được nhưng chậm đáng kể.
Muốn biết vì sao phép quay lại đủ? Cơ chế bên trong — phân bố Beta của toạ độ sau khi quay, bảng mã Lloyd–Max cố định nên không cần huấn luyện, tầng QJL 1 bit khử thiên lệch inner product, và cận dưới lý thuyết ≈ 2,7 lần — nằm ở 6.1 mục 6.
Các con số ở mục này là số Qdrant/paper công bố, không phải số đo trên
data/corpus.jsonlcủa repo này (50 tài liệu — quá nhỏ để quantization có ý nghĩa).
Chọn mức nén nào
Phần tiêu đề “Chọn mức nén nào”| Mức nén | Nên dùng | Ghi chú |
|---|---|---|
| 4× | Scalar (int8) | An toàn nhất; nhưng cân nhắc TQ 4-bit để được 8× cùng recall |
| 8× | TurboQuant 4-bit | Điểm ngọt hiện tại: recall ≈ scalar, nén gấp đôi |
| 16× | TQ 2-bit hoặc BQ 2-bit | TQ recall tốt hơn nhiều; BQ nhanh hơn |
| 24× | TQ 1.5-bit hoặc BQ 1.5-bit | Cùng đánh đổi như trên |
| 32× | TQ 1-bit hoặc BQ 1-bit | Recall gần nhau hơn ở mức này; BQ nhanh hơn |
| tới 64× | Product quantization | Chỉ khi bộ nhớ là ưu tiên số một |
| — | Scalar | Nếu bạn dùng khoảng cách L1 (TQ/BQ không phù hợp) |
Ba núm phải biết trước khi bật quantization
Phần tiêu đề “Ba núm phải biết trước khi bật quantization”rescore— lấy top-k thô bằng vector nén, rồi tính lại điểm bằng vector gốc. Bật mặc định cho BQ và TQ 1 / 1.5 / 2-bit. Đây là thứ mua lại phần lớn recall đã mất — nhưng nó đòi vector gốc vẫn truy cập được. Nếu vector gốc nằm trên disk chậm, rescore giết latency; khi đó Qdrant khuyên tắt rescore và tăng oversampling.oversampling— lấyoversampling × limitứng viên trước khi rescore (ví dụ 2.0, 4.0). Đây là núm đổi latency lấy chất lượng tại runtime, không cần build lại index — vai trò giốngefSearchcủa HNSW.quantile(chỉ scalar) — mặc định 0.99: bỏ 1% giá trị cực trị khi tính khoảng [min, max] để map sang int8. Hạ xuống nếu embedding có outlier làm hỏng thang đo.
Ngoài ra memory quyết định bản nào nằm RAM (pinned) bản nào nằm disk (cold). Ba cấu
hình thực dụng: (1) gốc cached + nén trong RAM — nhanh nhất; (2) gốc cold + nén pinned —
cân bằng, cân nhắc tắt rescore; (3) tất cả cold — nhỏ nhất, cần NVMe.
Muốn biết quantization đang lấy đi bao nhiêu recall: chạy lại đúng bộ truy vấn với
params: {quantization: {ignore: true}} rồi so hai con số. Đừng bật quantization mà không
đo bước này.
⚠️ Nhớ rằng bản nén là bản thêm vào, không phải bản thay thế — trừ khi bạn đẩy vector gốc
sang cold. Bật quantization mà vẫn giữ vector gốc trong RAM thì bộ nhớ tăng, chỉ tốc độ
mới lợi.
⚠️ Bẫy lớn nhất: filter + vector search
Phần tiêu đề “⚠️ Bẫy lớn nhất: filter + vector search”Bạn muốn “tìm 10 chunk giống nhất trong phòng ban X”. Hai cách sai:
- Post-filter: lấy top-100 bằng vector, rồi lọc phòng ban X. Nếu X chỉ chiếm 1% corpus thì top-100 có thể còn 0 kết quả. Recall sập không báo lỗi.
- Pre-filter naive: lọc trước rồi brute-force trong tập con. Đúng nhưng chậm khi tập con lớn.
Cách đúng: filtered ANN — engine phải hỗ trợ điều hướng đồ thị có ràng buộc (Qdrant, Weaviate, pgvector với điều kiện) và bạn phải đo recall khi có filter, không chỉ đo recall khi không filter. Cơ chế đầy đủ, và quy trình đo bốn số để tự phát hiện mình có đang bị hay không: 7.8 — Filterable HNSW và query planner. Đây là chỗ hầu hết hệ RAG có filter đang bị mất recall âm thầm.
Toán bộ nhớ (làm trước khi chọn hạ tầng)
Phần tiêu đề “Toán bộ nhớ (làm trước khi chọn hạ tầng)”1M chunk × 1536 chiều × 4 byte (float32) = 6.1 GB vector + HNSW M=32 ≈ 0.3 GB đồ thị + payload/metadata tuỳ→ scalar int8 (4×) = 1.5 GB→ TurboQuant 4-bit (8×) = 0.77 GB→ binary / TurboQuant 1-bit (32×) = 0.19 GB→ PQ x64 = 0.10 GB→ hoặc hạ 384 chiều (MRL) rồi + int8 = 0.4 GBLưu ý: các dòng nén là kích thước bản nén, cộng thêm vào vector gốc nếu bạn còn giữ
vector gốc trong RAM (xem núm memory ở trên).