Bỏ qua để đến nội dung
Search & RAG

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.

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 địnhThời điểmĐổi được lúc chạy?
m16xây❌ phải dựng lại
ef_construct100xây❌ phải dựng lại
payload_mnullxây❌ phải dựng lại
hnsw_ef (ef khi truy vấn)= ef_constructtruy vấnngay 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_ef trong tham số truy vấn. Đó là núm duy nhất không đòi dựng lại gì cả.

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ùng
Truy 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.0

Dò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.

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.

Từ v1.13, Qdrant xây HNSW được trên GPU. Ba điều đáng biết:

  1. 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.
  2. Nó chỉ tăng tốc việc xây index, không tăng tốc truy vấn.
  3. 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 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.

Triệu chứngVặn cái gì trướcGhi chú
Recall thấp, latency còn dưtăng hnsw_efKhông cần dựng lại
Recall vẫn thấp sau khi tăng hnsw_ef nhiềutăng m và/hoặc ef_constructPhải dựng lại; tốn RAM
Recall thấp chỉ khi có filter7.8Đây là bệnh khác hẳn
Latency cao, recall thừagiảm hnsw_efMiễn phí
RAM chậtgiảm m, hoặc datatype: float16, hoặc quantizationXem 7.10
Xây index quá lâugiảm max_segment_size, hoặc GPUXem 7.6
Phần 7 — Qdrant