7.7 HNSW trong Qdrant
Tầng 5 đã giải thích HNSW là gì: đồ thị nhiều tầng, tầng trên thưa để đi xa nhanh, tầng dưới dày để tinh chỉnh, tìm kiếm là greedy descent. Mục này không lặp lại chuyện đó. Nó trả lời câu hỏi hẹp hơn: cài đặt của Qdrant khác gì, và bạn vặn núm nào.
Bốn tham số, ba thời điểm
Phần tiêu đề “Bốn tham số, ba thời điểm”Nhầm lẫn phổ biến nhất là gộp chung các tham số “lúc xây” với tham số “lúc truy vấn”. Chúng khác nhau ở một điểm cực kỳ thực tế: đổi tham số lúc xây thì phải dựng lại index; đổi tham số lúc truy vấn thì có hiệu lực ngay.
| Tham số | Mặc định | Thời điểm | Đổi được lúc chạy? |
|---|---|---|---|
m | 16 | xây | ❌ phải dựng lại |
ef_construct | 100 | xây | ❌ phải dựng lại |
payload_m | null | xây | ❌ phải dựng lại |
hnsw_ef (ef khi truy vấn) | = ef_construct | truy vấn | ✅ ngay lập tức |
(Mặc định lấy từ config.yaml v1.19.0.)
Nếu chỉ nhớ một điều: khi cần recall cao hơn ngay bây giờ, tăng
hnsw_eftrong tham số truy vấn. Đó là núm duy nhất không đòi dựng lại gì cả.
m — số cạnh mỗi node
Phần tiêu đề “m — số cạnh mỗi node”Cao hơn ⇒ đồ thị dày hơn ⇒ recall tốt hơn, nhưng tốn RAM hơn và xây lâu hơn. RAM tốn thêm
xấp xỉ m × 8 byte cho mỗi vector.
Điều đáng biết: m: 0 tắt hẳn việc xây HNSW cho vector đó. Đây không phải trường hợp
suy biến vô dụng — nó là cấu hình đúng cho vector chỉ dùng để xếp lại chứ không để tìm,
ví dụ vector ColBERT (7.3). Rescore không đi qua
HNSW, nên đồ thị đó là RAM vứt đi.
ef_construct — độ rộng tìm kiếm lúc xây
Phần tiêu đề “ef_construct — độ rộng tìm kiếm lúc xây”Cao hơn ⇒ đồ thị chất lượng hơn ⇒ recall tốt hơn ở cùng hnsw_ef. Bạn chỉ trả giá một
lần, lúc xây. Vì vậy đây là chỗ nên hào phóng nếu bạn nạp dữ liệu một lần rồi đọc nhiều.
Có một mẹo vận hành ẩn trong tham số này: tăng ef_construct lên 1 là cách buộc Qdrant
dựng lại đồ thị HNSW. Bạn cần điều đó khi đã lỡ tạo payload index sau khi nạp dữ liệu
(7.9).
hnsw_ef — núm bạn thật sự dùng hằng ngày
Phần tiêu đề “hnsw_ef — núm bạn thật sự dùng hằng ngày”Đây là efSearch của Tầng 5. Nó quyết định đi bao rộng lúc truy vấn, và nó là núm đổi
latency lấy recall tại runtime.
Cách dùng đúng trong sản phẩm:
Truy vấn thường hnsw_ef nhỏ → nhanh, recall đủ dùngTruy vấn có filter chặt hnsw_ef lớn → bù lại phần đồ thị bị filter làm đứtĐo baseline / gỡ lỗi exact: true → brute-force, recall = 1.0Dòng cuối cùng là công cụ chẩn đoán quan trọng nhất mà nhiều người không biết Qdrant có:
exact: true bỏ qua HNSW và quét toàn bộ. Chạy cùng một bộ truy vấn hai lần — một lần
exact: true, một lần không — rồi so kết quả. Chênh lệch chính là phần recall mà ANN
đang lấy đi của bạn.
Đừng bao giờ bật quantization hay chỉnh HNSW mà không có phép đo này. Nó là baseline duy nhất bạn có, và nó rẻ (chỉ tốn thời gian trên một tập truy vấn nhỏ). Nguyên tắc này giống hệt tinh thần của Phần 2 — Metrics: không đo thì mọi tối ưu là mê tín.
full_scan_threshold_kb — khi Qdrant tự bỏ HNSW
Phần tiêu đề “full_scan_threshold_kb — khi Qdrant tự bỏ HNSW”Mặc định 10000. Ý nghĩa: khi số điểm ước tính mà truy vấn phải xét nhỏ hơn ngưỡng này,
query planner bỏ HNSW và quét thẳng.
Nghe ngược đời, nhưng đúng: HNSW có chi phí cố định để đi từ tầng trên xuống. Trên tập nhỏ, quét thẳng vừa nhanh hơn vừa cho recall 1.0. Đây chính là lý do corpus 50 tài liệu của cẩm nang không đo được gì về HNSW — Qdrant sẽ không dùng HNSW trên tập đó dù bạn có bật.
Ngưỡng này quan trọng nhất khi có filter, và đó là chủ đề của 7.8.
Và lại một lần nữa: đơn vị là KB, quy ước 1 KB ≈ 1 vector 256 chiều. Trên embedding
1536 chiều, 10000 KB tương ứng khoảng 1.700 điểm.
max_indexing_threads — cảnh báo trong chính config
Phần tiêu đề “max_indexing_threads — cảnh báo trong chính config”Mặc định 0 = tự chọn. Nhưng comment trong config.yaml có một câu đáng đọc kỹ:
“Best to keep between 8 and 16 to prevent likelihood of building broken/inefficient HNSW graphs.”
Nghĩa là nhiều luồng hơn không phải luôn tốt hơn: xây HNSW song song quá mức có thể tạo ra đồ thị kém chất lượng. Nếu bạn chạy trên một máy rất nhiều nhân và tự đặt tham số này cao, đây là chỗ bạn có thể vô tình làm hỏng recall mà không hề có thông báo lỗi nào.
Index HNSW nằm ở đâu
Phần tiêu đề “Index HNSW nằm ở đâu”Từ v1.19, thay vì on_disk: true/false, cấu hình nên dùng là memory với ba mức
pinned / cached / cold. Đồ thị HNSW là một cấu trúc truy cập ngẫu nhiên rất nhiều
— nó nhảy từ node này sang node kia — nên đẩy nó xuống đĩa là quyết định đắt hơn đẩy vector
xuống đĩa.
Qdrant có một cơ chế riêng để chữa việc này (inline storage, từ v1.16): nhúng vector đã nén thẳng vào node HNSW, để một lần đọc trang 4 KB lấy được cả láng giềng lẫn vector thay vì hai lần đọc rải rác. Chi tiết và con số ở 7.10.
Xây HNSW bằng GPU
Phần tiêu đề “Xây HNSW bằng GPU”Từ v1.13, Qdrant xây HNSW được trên GPU. Ba điều đáng biết:
- Cài đặt là của riêng Qdrant và dựa trên Vulkan, nên nó không khoá bạn vào CUDA / NVIDIA.
- Nó chỉ tăng tốc việc xây index, không tăng tốc truy vấn.
- Mức tăng tốc Qdrant công bố dao động khá rộng tuỳ cấu hình (số dẫn từ nguồn ngoài): từ khoảng 4× tới ~10× so với CPU.
Nói cách khác: GPU chữa triệu chứng của 7.6 — backlog indexing — chứ không làm truy vấn của bạn nhanh hơn. Nếu bệnh của bạn là latency truy vấn, GPU không phải thuốc.
Bảng vặn núm
Phần tiêu đề “Bảng vặn núm”| Triệu chứng | Vặn cái gì trước | Ghi chú |
|---|---|---|
| Recall thấp, latency còn dư | tăng hnsw_ef | Không cần dựng lại |
Recall vẫn thấp sau khi tăng hnsw_ef nhiều | tăng m và/hoặc ef_construct | Phải dựng lại; tốn RAM |
| Recall thấp chỉ khi có filter | → 7.8 | Đây là bệnh khác hẳn |
| Latency cao, recall thừa | giảm hnsw_ef | Miễn phí |
| RAM chật | giảm m, hoặc datatype: float16, hoặc quantization | Xem 7.10 |
| Xây index quá lâu | giảm max_segment_size, hoặc GPU | Xem 7.6 |