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

7.12 Hybrid: RRF và DBSF

Tầng 2 — Hybrid & Fusion đã nói vì sao hợp nhất hai nhánh lại giúp, và khi nào nó không giúp. Mục này nói về hai cách fusion mà Qdrant cài sẵn, công thức thật của chúng, và một cái bẫy riêng của chế độ phân tán mà tài liệu chung về hybrid không nhắc tới.

Bài toán: cộng hai thang đo không cùng đơn vị

Phần tiêu đề “Bài toán: cộng hai thang đo không cùng đơn vị”
BM25 trả về: d008 → 12,4 d031 → 9,1 d012 → 7,8
Cosine trả về: d008 → 0,71 d019 → 0,68 d031 → 0,52

Cộng thẳng thì BM25 nuốt chửng cosine — không phải vì nó đúng hơn, mà vì số của nó to hơn. Chuẩn hoá bằng min-max cũng không cứu được: BM25 không có trần, và trần của nó thay đổi theo từng truy vấn.

Hai lời giải, hai triết lý ngược nhau:

RRFDBSF
Đọc cái gìchỉ thứ hạngđiểm số
Vứt đi cái gìđộ lớn của khoảng cáchkhông vứt gì
Bền với thang đo lệchrấtvừa
Bền với ngoại lairấtkém
Có sẵn từv1.10v1.10

Công thức Qdrant cài đặt:

score(d)=rd1k+rd\mathrm{score}(d) = \sum_{r_d} \frac{1}{k + r_d}

tổng chạy trên thứ hạng rdr_d của ddmỗi danh sách có chứa dd.

r_d là thứ hạng của d trong một danh sách, đếm từ 0, và k mặc định là 2 (cấu hình được từ v1.16).

Tính bằng tay cho ví dụ trên, k = 2:

dochạng BM25hạng cosineRRF
d008001/2 + 1/2 = 1,000
d031121/3 + 1/4 = 0,583
d01221/4 = 0,250
d01911/3 = 0,333

Thứ tự ra: d008, d031, d019, d012.

Ba điều đọc ra được từ bảng này:

  1. Xuất hiện ở cả hai danh sách là một lợi thế lớn. d008 thắng vì cả hai nhánh đều xếp nó đầu. Đây chính là tính chất bạn muốn ở hybrid.
  2. Điểm gốc bị vứt hoàn toàn. d031 có BM25 = 9,1 (rất cao) nhưng chỉ được tính là “hạng 1”. Nếu độ lớn ấy có nghĩa, RRF đang phí thông tin.
  3. k nhỏ làm đỉnh dốc hơn. k = 2 khiến chênh lệch giữa hạng 0 và hạng 1 rất lớn (0,5 so với 0,333). k lớn làm mọi thứ phẳng lại. Đây là núm ít người vặn nhưng có tác dụng thật.

Từ v1.17RRF có trọng số: mỗi prefetch nhân thêm một hệ số. Cảnh báo của chính Qdrant, và đáng nghe:

Trọng số là một lựa chọn cấu hình, không phải thứ vặn tuỳ hứng. Cách đáng tin duy nhất để đặt nó là thử trên dữ liệu của bạn với tập train/val tách riêng.

Nói cách khác: chỉnh trọng số bằng cảm giác trên vài truy vấn tự nghĩ ra là cách nhanh nhất để overfit vào chính những truy vấn đó. Xem Phần 2 — 6. Ý nghĩa thống kê.

DBSF đưa hai danh sách về cùng một thang bằng cách dùng trung bình và độ lệch chuẩn của chính danh sách đó trong truy vấn hiện tại:

s^=s(μ3σ)6σ\hat{s} = \frac{s - (\mu - 3\sigma)}{6\sigma}

Tức là: coi khoảng [μ − 3σ, μ + 3σ] là toàn bộ dải, rồi chiếu tuyến tính về [0, 1]. Sau đó cộng hai điểm đã chuẩn hoá lại.

Bốn đặc tính cần biết:

  1. Điểm không bị cắt về [0, 1]. Giá trị ngoài dải 3σ vẫn nằm ngoài. Đây là chủ ý — một tài liệu vượt trội thật sự vẫn giữ được ưu thế của nó.
  2. Khi mọi điểm bằng nhau, kết quả là 0,5 (tránh chia cho 0).
  3. Tính lại cho mỗi truy vấn, chỉ dựa trên top-k hiện tại. Không có trạng thái lưu giữa các truy vấn.
  4. Vì (3), một ngoại lai duy nhất trong top-k làm lệch chuẩn hoá. Qdrant khuyên: nếu thấy thứ hạng không ổn định, tăng limit của prefetch để μ và σ được ước lượng trên mẫu lớn hơn.

Khác biệt cốt lõi so với RRF: DBSF giữ được “thắng bao xa”. Một tài liệu bỏ xa phần còn lại trong một nhánh sẽ mang lợi thế đó vào kết quả fusion. RRF thì không — với RRF, thắng sát nút và thắng cách biệt đều chỉ là “hạng 0”.

Không cái nào thắng tuyệt đối. Khung quyết định của Qdrant, viết lại cho gọn:

Tình huống của bạnChọn
Có golden set, có thể tách train/valRRF có trọng số, chỉnh trọng số trên tập train
Không có golden set, nhưng tin rằng điểm số của hai nhánh có nghĩa và được hiệu chuẩnDBSF
Không có golden set, không biết gì về điểm sốRRF — mặc định an toàn
Điểm số một nhánh hay có ngoại laiRRF (DBSF nhạy với ngoại lai)

Và một câu nên nhắc lại từ Tầng 2:

Hybrid không phải lúc nào cũng giúp. Nếu nhánh sparse của bạn kém, fusion chỉ kéo nhánh dense xuống. Đo từng nhánh riêng trước, rồi mới đo bản fusion.

⚠️ Bẫy phân tán: fusion tính theo từng shard

Phần tiêu đề “⚠️ Bẫy phân tán: fusion tính theo từng shard”

Đây là chi tiết ít được nhắc mà hậu quả thì lớn, và nó chỉ xuất hiện khi collection có nhiều shard:

Nếu fusion nằm bên trong một prefetch, mỗi shard tự hợp nhất kết quả cục bộ của nó. Thứ hạng sau fusion là theo từng shard, không phải toàn cục.

Muốn fusion toàn cục, fusion phải là truy vấn chính.

SAI (khi nhiều shard): ĐÚNG:
{ {
"prefetch": { "prefetch": [ {dense}, {sparse} ],
"prefetch": [{dense},{sparse}], "query": { "fusion": "rrf" }
"query": {"fusion":"rrf"} }
},
"query": <rerank>
}
⇒ fusion chạy trong từng shard ⇒ fusion chạy trên kết quả gộp

Hệ quả xấu tính: hệ của bạn chạy đúng suốt thời gian dev trên một shard, rồi thay đổi hành vi khi lên production nhiều shard — mà không có lỗi nào. Nếu bạn đang xây cascade “hybrid rồi rerank” trên cụm phân tán, đọc lại đoạn này hai lần.

Formula chồng lên fusion: cẩn thận độ lớn

Phần tiêu đề “Formula chồng lên fusion: cẩn thận độ lớn”

Mẫu rất hay dùng: fusion bằng RRF trong prefetch, rồi bọc bằng một formula để thêm logic nghiệp vụ (ưu tiên tài liệu mới, đẩy sản phẩm phổ biến lên — xem 7.14).

Cái bẫy nằm ở độ lớn:

Điểm RRF là tổng của các 1/(k + hạng)rất nhỏ (ví dụ trên: 1,0 là điểm cao nhất có thể khi có hai danh sách và k=2). Trong khi hàm decay trả về giá trị trong [0, 1]. Cộng thẳng thì thành phần nghiệp vụ nuốt chửng thành phần liên quan.

Phải nhân thành phần decay với một hệ số nhỏ. Và phải đo lại sau khi làm — đây đúng là loại thay đổi làm nDCG tụt mà không ai nhận ra, vì kết quả “trông vẫn hợp lý”.

Phần 7 — Qdrant