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

5.11 Cắm reranker vào một hệ thống thật

Mục này về kiến trúc: reranker gọi ở đâu, ai chịu latency, chuyện gì xảy ra khi nó chết, và làm sao trộn điểm liên quan với các tín hiệu nghiệp vụ mà không phá cả hai.

Ba chỗ có thể đặt nó, ba hệ quả khác nhau

Phần tiêu đề “Ba chỗ có thể đặt nó, ba hệ quả khác nhau”
(A) TRONG search engine (B) Ở tầng ứng dụng (C) Trong agent loop
ES/OpenSearch/Vespa app gọi 2 lần rerank như một tool
query ─► engine query ─► engine ─► k doc agent quyết định
├─ retrieve k │ có cần rerank không
└─ rerank k ─► n app ─► reranker ─► n
1 lần gọi mạng 2 lần gọi mạng n lần gọi, khó dự đoán
không kiểm soát chi tiết kiểm soát hoàn toàn linh hoạt nhất
không lấy được điểm thô thấy được mọi điểm đắt và khó debug
(A) trong engine(B) tầng ứng dụng(C) trong agent
Số lần round-trip12không xác định
Đổi model rerankerphải sửa cấu hình engineđổi một biến môi trườngtuỳ prompt
Thấy điểm thô để logthường không
Fallback khi reranker chếtengine lo (hoặc cả query chết)bạn tự chủ độngphức tạp
Phù hợphệ đã dùng engine đó làm trung tâmmặc định cho RAGagent multi-hop

Nên áp dụng vào AI Agent: bắt đầu bằng (B). Nó cho bạn log điểm thô — thứ bạn cần cho 5.35.13 — và cho bạn quyền tắt reranker bằng một biến môi trường lúc 2 giờ sáng.

Cascade: nhiều tầng, mỗi tầng đắt hơn và hẹp hơn

Phần tiêu đề “Cascade: nhiều tầng, mỗi tầng đắt hơn và hẹp hơn”

Khi corpus lớn và budget latency chặt, một tầng rerank là không đủ mềm. Mẫu chuẩn:

1M doc ──BM25 + vector──► 1000 ──reranker nhỏ, nhanh──► 100 ──reranker lớn──► 10 ──► LLM
~10 ms ~20 ms ~50 ms

Nguyên tắc của cascade: mỗi tầng chỉ cần đủ tốt để không đánh rơi tài liệu đúng, không cần xếp đúng thứ tự cuối. Tầng cuối mới cần chính xác. Điều đó cho phép tầng giữa dùng model nhỏ, quantize, seq length ngắn.

Chỉ làm cascade khi có bằng chứng cần: nó thêm một điểm hỏng, một model phải theo dõi, và một chỗ nữa để phân phối điểm trôi.

Rerank có điều kiện: đừng gọi khi không cần

Phần tiêu đề “Rerank có điều kiện: đừng gọi khi không cần”

Không phải truy vấn nào cũng cần rerank. Trên corpus cẩm nang, lớp A đã đạt nDCG 0.971 với BM25 thuần (baseline đã đo) — rerank các truy vấn đó là trả tiền để có nguy cơ làm hỏng.

Các tín hiệu quyết định bỏ qua rerank, rẻ và đo được:

Tín hiệuNgưỡng gợi ýLý do
Truy vấn chứa mã định danh (E2003, sk_live, POST /v1/...)regex khớpLexical đã đúng; reranker hay ưu tiên văn xuôi “đọc thấy hợp” hơn
Điểm stage 1 của top-1 bỏ xa top-2s1 ≥ 2·s2Đáp án đã rõ ràng
Chỉ có ≤ n ứng viên trả vềk ≤ nKhông có gì để xếp lại
Truy vấn là điều hướng (“mở trang thanh toán”)phân loại ý địnhKhông phải bài toán liên quan ngữ nghĩa

Ngược lại, các tín hiệu nên rerank: câu hỏi dài dạng tự nhiên, không có từ khoá trùng, nhiều ứng viên điểm sát nhau, hoặc truy vấn đến từ lớp paraphrase/multi-hop.

Reranker là ứng viên cache tốt vì đầu vào rời rạc và đầu ra nhỏ (một số).

khoá = hash(model_id + version, query_đã_chuẩn_hoá, doc_id, doc_content_hash)
giá trị = điểm

Bốn thành phần của khoá, không được thiếu cái nào:

Thành phầnBỏ đi thì sao
model_id + versionNâng cấp model → phục vụ điểm của model cũ. Lỗi im lặng, rất khó truy
query đã chuẩn hoáCache miss vô ích với mọi khác biệt hoa/thường, khoảng trắng
doc_id— (bắt buộc hiển nhiên)
doc_content_hashTài liệu được sửa → điểm cũ vẫn được dùng, nội dung mới không được đánh giá

Tỷ lệ hit thực tế phụ thuộc hoàn toàn vào phân phối truy vấn của bạn: hệ hỏi-đáp nội bộ có đuôi truy vấn lặp rất dài (hit cao), hệ tìm kiếm mở gần như không lặp (hit thấp). Đo, đừng đoán. Ba tầng cache của một hệ RAG nói ở Tầng 10 — Production.

Reranker chết thì sao: fallback là một quyết định sản phẩm

Phần tiêu đề “Reranker chết thì sao: fallback là một quyết định sản phẩm”

Reranker là một lời gọi mạng hoặc một GPU — nó sẽ chết, sẽ chậm, sẽ hết quota. Ba lựa chọn, và bạn phải chọn trước khi sự cố xảy ra:

Chiến lượcNgười dùng thấyKhi nào đúng
Degrade — dùng luôn thứ tự stage 1Kết quả tệ hơn, vẫn có kết quảMặc định. Search kém vẫn tốt hơn không có search
Fail — trả lỗiThông báo lỗiKhi trả lời sai đắt hơn không trả lời (y tế, pháp lý, tài chính)
Retry rồi degradeChậm hơn rồi tệ hơnChỉ khi timeout ngắn và budget còn chỗ

Kèm theo, ba thứ bắt buộc phải có:

timeout cứng cho lời gọi rerank (ví dụ 200 ms — nhỏ hơn budget tổng)
circuit breaker (lỗi liên tiếp → tắt rerank N phút, đừng thử mãi)
metric riêng: tỷ lệ degrade ← nếu không đo, bạn sẽ chạy tháng trời KHÔNG có rerank

Dòng cuối là một chế độ hỏng thật và rất phổ biến: reranker âm thầm timeout 100% trong nhiều tuần, hệ thống vẫn trả kết quả, không ai biết chất lượng đã tụt về stage 1.

Trộn điểm liên quan với tín hiệu nghiệp vụ

Phần tiêu đề “Trộn điểm liên quan với tín hiệu nghiệp vụ”

Trong sản phẩm thật, “liên quan nhất” không phải luôn là “nên đưa lên đầu”: tài liệu mới hơn, phiên bản đang dùng của khách, ngôn ngữ của người dùng, hàng còn tồn kho — đều là tín hiệu thật.

Cách làm sai: cộng thẳng điểm reranker với điểm nghiệp vụ. Hai thang không cùng đơn vị và không hiệu chuẩn (5.3) — kết quả là một hàm không ai hiểu, và mỗi lần đổi model reranker là một lần trọng số nghiệp vụ ngầm đổi theo.

Ba cách đúng, xếp theo độ phức tạp:

CáchLàm gìƯu / nhược
Lọc cứng trướcBỏ tài liệu không đủ điều kiện trước khi rerank (sai ngôn ngữ, đã archive, không thuộc tenant)Đơn giản, đúng, rẻ. Làm cái này trước tiên. Cảnh báo: lọc + vector có bẫy riêng — Tầng 5
Xếp lại theo bậcRerank ra thứ tự liên quan, rồi trong các tài liệu xấp xỉ ngang điểm mới ưu tiên theo nghiệp vụGiữ được nghĩa của từng tín hiệu, dễ giải thích
Học một model ranking riêngĐưa điểm reranker vào làm một feature của một model LTR có cả tín hiệu nghiệp vụMạnh nhất, cần dữ liệu nhãn/log và một vòng huấn luyện (5.14)

Chú ý cấu trúc của cách thứ ba: reranker không bị thay thế, nó trở thành một cột đầu vào. Đó là cách các hệ ranking lớn dùng — điểm liên quan ngữ nghĩa là một feature mạnh trong một hàm ranking rộng hơn.

Mặc định của các nền tảng — đọc kỹ, chúng khác nhau nhiều

Phần tiêu đề “Mặc định của các nền tảng — đọc kỹ, chúng khác nhau nhiều”

Nếu bạn dùng reranker qua một search engine hay vector DB, các giá trị mặc định dưới đây quyết định hành vi hệ thống của bạn, và chúng không giống nhau:

Nền tảngCơ chếMặc định đáng biết
Vespafirst-phasesecond-phaseglobal-phasesecond-phase chạy trên 100 hit tốt nhất mỗi content node; global-phase chạy trên 100 hit tốt nhất toàn cục. Có total-rerank-count làm chặn trên cứng
Elasticsearchretriever text_similarity_rerankerrank_window_size mặc định 10 — rất nhỏ. Và nếu không truyền inference_id, nó dùng .rerank-v1-elasticsearch, tức một model chỉ tiếng Anh (5.8)
OpenSearchrerank processor trong search pipelineBốn kiểu: cross-encoder (ml_opensearch), by_field, và hai kiểu kết hợp
Milvusmodel ranker qua Function (2.6.x)BGERerankFunction mặc định bge-reranker-v2-m3; CohereRerankFunction mặc định rerank-english-v2.0một mặc định đã cũ, đừng để nguyên
Weaviatemodule reranker-cohere / reranker-transformersreranker-transformers không có trên Weaviate Cloud vì cần chạy container model riêng
Qdrantkhông có reranker phía serverRerank là việc của tầng ứng dụng — cross-encoder hoặc late interaction do bạn gọi
LlamaIndexnode postprocessorSentenceTransformerRerank mặc định cross-encoder/ms-marco-TinyBERT-L-2-v2 — model rất nhỏ, tiếng Anh
LangChainContextualCompressionRetrieverReranker được bọc thành “document compressor”

Hai dòng đáng gạch chân: rank_window_size = 10 của Elasticsearch nghĩa là mặc định bạn chỉ rerank 10 kết quả — gần như không có tác dụng gì, vì thứ tự top-10 phần lớn đã do stage 1 quyết. Và các mặc định model (.rerank-v1 tiếng Anh, rerank-english-v2.0, ms-marco-TinyBERT) đều là model tiếng Anh nhỏ. Trên corpus tiếng Việt, chúng vẫn chạy và vẫn trả về điểm — chỉ là điểm đó không có nghĩa.

Một quan sát về kiến trúc đáng học từ Vespa: các tín hiệu nghiệp vụ rẻ thuộc về first-phase/second-phase (rộng, chạy trên nhiều tài liệu), còn cross-encoder ngữ nghĩa đắt thuộc về global-phase sau khi đã gộp kết quả. Tức là ngược với trực giác thường thấy: liên quan ngữ nghĩa là tầng cuối và hẹp nhất, không phải tầng đầu.

Đáng đọc vì nó là ví dụ đầy đủ về các quyết định của mục này, đo trên hệ thống thật (một sản phẩm hỗ trợ khách hàng dùng LLM làm reranker):

k = 40 ứng viên
chia thành 4 shard song song (~10 đoạn/shard, ~200 token/đoạn)
shard nào timeout → fallback: rerank lát đó bằng cross-encoder BGE
round-robin batching → giảm thiên lệch vị trí
theo dõi p95/p99 trên dashboard

Kết quả họ báo cáo: lần triển khai đầu thêm +5 giây; sau tối ưu còn dưới 1 giây (p50 +0,9 s) và chi phí giảm khoảng 8 lần — trong đó 28% tiết kiệm token đầu ra đến từ việc đổi định dạng, và thêm 50% từ việc đặt ngưỡng.

Chú ý hai chi tiết thiết kế: (1) họ nói thẳng xác suất timeout tăng theo số lời gọi song song — chia shard mua được latency nhưng bán đi độ tin cậy, nên phải có fallback; (2) fallback của họ là một model khác, rẻ hơn, không phải bỏ rerank hoàn toàn. Đó là một bậc degrade tinh hơn bảng ở trên.

Reranker tối ưu từng cặp độc lập, nên nó rất dễ trả về 5 đoạn gần như trùng nhau — lãng phí context window và tệ hại cho câu hỏi multi-hop (lớp C), nơi bạn cần các tài liệu khác nhau.

MMR (Maximal Marginal Relevance) chọn tuần tự, mỗi bước cân giữa liên quan và mới:

MMR=argmaxd[λrel(q,d)(1λ)maxd đa˜ chọnsim(d,d)]\mathrm{MMR} = \arg\max_{d} \left[\, \lambda \cdot \mathrm{rel}(q,d) - (1-\lambda) \cdot \max_{d' \text{ đã chọn}} \mathrm{sim}(d,d') \,\right]
λNghĩa
1thuần liên quan (bỏ đa dạng)
≈ 0,7điểm khởi đầu hợp lý
0thuần đa dạng (bỏ liên quan)

Đặt nó sau reranker, dùng rel là điểm reranker và sim là cosine giữa embedding của các đoạn. Chi phí không đáng kể vì chỉ chạy trên n đoạn cuối. Một cách rẻ hơn nữa mà thường đủ: chặn không lấy quá 1–2 đoạn từ cùng một tài liệu gốc.

Phần 5 — Reranker