RAG nâng cao 4 — Reranking: tinh lọc kết quả truy hồi

13 thg 7, 2026 3 lượt xem
#ai
#rag
#retrieval
#reranking
#cross-encoder

RAG nâng cao 3 — Hybrid Search ta đã kéo được nhiều đoạn văn bản đúng vào top-N bằng cách ghép BM25 với vector — tối ưu recall. Nhưng thứ tự trong top-N vẫn còn thô: đoạn thật sự trả lời được câu hỏi có thể nằm ở hạng 12, trong khi hạng 1–3 lại là những đoạn "na ná". LLM chỉ đọc được vài đoạn đầu, nên nếu đoạn vàng không ở trên đầu, câu trả lời vẫn sai. Bài này giải quyết bước tinh lọc đó: reranking — chấm lại và sắp xếp lại top-N để tối ưu precision ở đỉnh bảng.

Vì sao retrieval cần được rerank

Điểm mấu chốt nằm ở cách các hệ retrieval nhanh tính điểm liên quan. Chúng dùng bi-encoder: nhúng câu hỏi thành một vector, nhúng mỗi tài liệu thành một vector — độc lập với nhau, từ trước — rồi đo độ gần (cosine). Vì tài liệu được nhúng offline lúc index, tại thời điểm truy vấn chỉ cần nhúng câu hỏi và tra ANN, nên rất nhanh và rẻ, quét được hàng triệu đoạn.

Cái giá của tốc độ đó là độ thô. Khi nén cả một đoạn văn bản vào một vector cố định mà chưa biết câu hỏi là gì, mô hình buộc phải "trung bình hoá" mọi khía cạnh của đoạn. Nó không thể tập trung vào đúng phần liên quan tới câu hỏi cụ thể, vì lúc nhúng tài liệu, câu hỏi chưa tồn tại. Kết quả là điểm cosine chỉ là xấp xỉ độ liên quan — đủ tốt để lọc từ hàng triệu xuống ~100 ứng viên, nhưng không đủ tinh để chọn đúng 3–5 đoạn tốt nhất.

Ví dụ ngân hàng: câu hỏi "phí tất toán trước hạn khoản vay tín chấp là bao nhiêu". Một đoạn dài nói chung về sản phẩm vay tín chấp (điều kiện, lãi suất, hồ sơ) có vector rất gần câu hỏi vì trùng nhiều chủ đề, nhưng lại không chứa mức phí tất toán. Một đoạn ngắn khác nói đúng "phí tất toán trước hạn 1% dư nợ còn lại" có thể ở hạng thấp hơn vì ngắn, ít từ trùng chủ đề. Bi-encoder khó phân biệt hai đoạn này; rerank thì phân biệt được.

Ý tưởng của reranking: sau khi retrieval (rẻ) đã thu hẹp còn top-N, ta bỏ ra một mô hình mạnh hơn nhưng đắt hơn để chấm lại từng đoạn có nhìn thẳng vào câu hỏi. Vì chỉ chạy trên N đoạn (không phải cả kho), chi phí vẫn kiểm soát được.

Cross-encoder: chấm cả cặp (câu hỏi, tài liệu) cùng lúc

Cross-encoder là loại reranker phổ biến nhất. Khác biệt cốt lõi so với bi-encoder:

  • Bi-encoder: vector(câu hỏi)vector(tài liệu) tính riêng, rồi so cosine. Câu hỏi và tài liệu không bao giờ "gặp nhau" bên trong mô hình.
  • Cross-encoder: đưa cả câu hỏi lẫn tài liệu vào cùng một lượt qua mô hình (nối chúng thành một chuỗi [câu hỏi] [SEP] [tài liệu]), để cơ chế attention cho từng token của câu hỏi "nhìn" trực tiếp vào từng token của tài liệu. Đầu ra là một điểm liên quan duy nhất cho cặp đó.

Vì mô hình xử lý tương tác token-với-token giữa hai bên, nó nắm được những chi tiết mà việc nén-độc-lập bỏ sót: đoạn có đúng chứa mức phí không, con số có khớp kỳ hạn hỏi không, phủ định ("không áp dụng cho...") có làm đảo nghĩa không. Nhờ vậy cross-encoder chính xác hơn nhiều bi-encoder về độ liên quan.

Cái giá: cross-encoder không tái sử dụng được tính toán. Với bi-encoder, vector tài liệu tính một lần lúc index rồi dùng mãi. Với cross-encoder, mỗi cặp (câu hỏi, tài liệu) phải chạy lại mô hình từ đầu vì điểm phụ thuộc cả hai. Chấm 1 câu hỏi với 1 triệu tài liệu = 1 triệu lượt forward — bất khả thi để làm retrieval. Đó là lý do cross-encoder chỉ dùng để rerank top-N đã được retrieval lọc sẵn.

Đánh đổi recall vs precision, và cách chọn N, k

Đây là mấu chốt kiến trúc của cả pipeline:

  • Retrieval lo recall: retrieve rộng (top-N lớn, ví dụ N = 50–100) để chắc chắn đoạn vàng lọt vào tập ứng viên. Nếu đoạn đúng không có trong top-N, rerank cũng vô phương — nó chỉ sắp xếp lại những gì được đưa cho.
  • Rerank lo precision: chấm lại N đoạn đó và giữ top-k nhỏ (ví dụ k = 3–8) để đẩy đoạn đúng nhất lên trên đầu, đưa vào LLM.

Chọn N là một đánh đổi trực tiếp: N lớn → recall cao hơn (ít bỏ sót đoạn đúng) nhưng latency và chi phí rerank tăng tuyến tính (rerank 100 đoạn tốn gấp đôi rerank 50). N nhỏ tiết kiệm nhưng rủi ro đoạn vàng bị rớt ngay từ retrieval. Thực tế thường bắt đầu N ≈ 50, đo recall trên tập câu hỏi có nhãn, tăng dần tới khi recall bão hoà. Chọn k phụ thuộc ngân sách token và giới hạn ngữ cảnh của LLM: k = 3–5 cho câu hỏi cần vài đoạn, k lớn hơn cho câu hỏi tổng hợp.

Đây là hình phễu: retrieval mở rộng miệng phễu để không bỏ sót; rerank và nén thu hẹp dần để chỉ những đoạn tốt nhất, gọn nhất chạm tới LLM.

Các lựa chọn reranker

Không chỉ có một loại reranker. Ba nhóm chính, khác nhau ở đánh đổi chất lượng — tốc độ — chi phí:

1) Cross-encoder (rerank model chuyên dụng)

Là lựa chọn mặc định, cân bằng tốt nhất cho đa số dự án. Có cả bản thương mại lẫn mã nguồn mở. Ví dụ (chỉ nêu để hình dung, không phải khuyến nghị cố định): Cohere Rerank (API thương mại), bge-rerankermxbai-rerank (mã nguồn mở, chạy được on-prem). Một reranker chuyên dụng thường là mô hình vài trăm triệu tham số — đủ nhỏ để chấm 50–100 đoạn trong vài chục tới vài trăm mili-giây trên GPU, đủ mạnh để cải thiện precision rõ rệt. Với ngân hàng cần chạy on-prem vì dữ liệu nhạy cảm, các reranker OSS là hướng thực tế.

2) ColBERT / late-interaction (token-level)

ColBERT đi giữa bi-encoder và cross-encoder. Nó nhúng từng token của câu hỏi và từng token của tài liệu thành vector riêng (thay vì một vector cho cả đoạn), rồi tính độ liên quan bằng late interaction: với mỗi token câu hỏi, tìm token tài liệu khớp nhất (MaxSim) rồi cộng lại. Vì vector token của tài liệu tính trước được lúc index (giống bi-encoder), nó nhanh hơn cross-encoder nhiều; nhưng vì so khớp ở mức token (giống cross-encoder), nó tinh hơn bi-encoder. Đánh đổi: tốn dung lượng lưu trữ (mỗi tài liệu thành nhiều vector token) và cần index chuyên biệt. ColBERT hợp khi cần chất lượng gần cross-encoder mà độ trễ phải rất thấp, hoặc dùng chính nó làm tầng retrieval tinh.

3) LLM-as-reranker

Nhờ chính một LLM đọc câu hỏi và danh sách đoạn rồi chấm điểm/xếp hạng độ liên quan (ví dụ yêu cầu chấm 0–10 mỗi đoạn, hoặc trả về thứ tự các id). Mạnh nhất về khả năng suy luận — hiểu được sắc thái, phủ định, câu hỏi nhiều ý — vì tận dụng năng lực ngôn ngữ đầy đủ của LLM. Nhưng đắt và chậm nhất: mỗi lần rerank là một (hoặc nhiều) lượt gọi LLM, tốn token và latency cao. Thường chỉ dùng cho N nhỏ, câu hỏi giá trị cao, hoặc khi độ chính xác quan trọng hơn chi phí. Cũng cần đề phòng LLM "bịa" điểm hoặc lệ thuộc thứ tự đầu vào.

LoạiCách chấmChất lượngTốc độ / Chi phíKhi nên dùng
Bi-encoder (retrieval)Nhúng độc lập, cosineThôRất nhanh, rất rẻLọc từ triệu xuống top-N
Cross-encoderCặp (Q, D) cùng lượtCaoTrung bình (chỉ top-N)Mặc định cho rerank
ColBERT / late-interactionToken-level MaxSimCaoNhanh, tốn lưu trữCần chất lượng + latency thấp
LLM-as-rerankerLLM chấm/xếp hạngRất caoChậm, đắtN nhỏ, câu hỏi giá trị cao

Sau rerank: MMR, nén ngữ cảnh, và thứ tự đoạn

Rerank cho ra top-k đúng nhất, nhưng trước khi đưa vào LLM còn ba tinh chỉnh quan trọng.

MMR — đa dạng hoá, giảm trùng lặp

Top-k sau rerank có thể trùng lặp: cả 5 đoạn đầu đều nói cùng một ý (ví dụ 5 phiên bản gần giống nhau của cùng điều khoản). Đưa 5 đoạn na ná vào LLM là lãng phí ngân sách ngữ cảnh mà không thêm thông tin. MMR (Maximal Marginal Relevance) chọn đoạn theo hai tiêu chí đồng thời: liên quan tới câu hỏi khác biệt với những đoạn đã chọn. Về mặt trực giác, MMR có tham số λ cân giữa "chọn đoạn liên quan nhất" và "chọn đoạn bổ sung thông tin mới". Kết quả là một top-k vừa đúng vừa đa dạng, phủ được nhiều khía cạnh của câu hỏi thay vì lặp một ý. Rất hữu ích khi kho có nhiều tài liệu gần trùng (các bản sửa đổi của cùng quy trình).

Contextual compression — nén ngữ cảnh

Mỗi đoạn (chunk) thường dài hơn phần thật sự cần. Nén ngữ cảnh (contextual compression) là bước cắt bớt: chỉ giữ lại câu/đoạn nhỏ thật sự liên quan tới câu hỏi trước khi đưa vào LLM. Có thể làm bằng một mô hình nhỏ trích câu liên quan, hoặc cắt theo điểm liên quan ở mức câu. Lợi ích kép: tiết kiệm token (giảm chi phí, tăng tốc) và giảm nhiễu (ít văn bản thừa thì LLM ít bị phân tâm, ít bịa). Đánh đổi: nén quá tay có thể cắt mất ngữ cảnh cần thiết — cần đo lại chất lượng câu trả lời sau khi bật nén.

Sắp xếp tránh "lost in the middle"

Có một hiện tượng đã được quan sát ở LLM: khi ngữ cảnh dài, mô hình chú ý tốt tới phần đầu và phần cuối, nhưng dễ bỏ sót thông tin nằm ở giữa — gọi là "lost in the middle". Hệ quả thực tế: nếu bạn có top-k đã xếp theo độ liên quan giảm dần và nhét thẳng vào prompt, đoạn liên quan thứ 3–4 rơi vào giữa và dễ bị "ngó lơ". Một mẹo phổ biến là sắp xếp lại thứ tự đưa vào: đặt các đoạn liên quan nhất ở đầu và cuối ngữ cảnh, đẩy đoạn kém quan trọng vào giữa. Đây là tinh chỉnh nhỏ nhưng miễn phí, đáng làm khi k lớn hoặc mỗi đoạn dài.

Chi phí và độ trễ: rerank không miễn phí

Rerank thêm một chặng xử lý nữa vào đường phản hồi, nên latency tăng. Vài đòn bẩy để giữ chi phí trong tầm kiểm soát:

  • Chọn N hợp lý: rerank tuyến tính theo N. Đừng rerank 200 đoạn nếu 60 đã đủ recall. Đo recall theo N rồi chốt điểm bão hoà.
  • Batch: gửi cả N cặp (câu hỏi, tài liệu) vào reranker trong một lô thay vì N lần gọi tuần tự — tận dụng song song của GPU/API, giảm mạnh tổng thời gian.
  • Caching: với câu hỏi lặp lại (FAQ, các câu hay hỏi), cache kết quả rerank hoặc cache theo cặp (câu hỏi chuẩn hoá, tài liệu). Tra cứu nội bộ ngân hàng có phân phối câu hỏi khá tập trung nên cache đạt hiệu quả tốt.
  • Reranker on-prem nhỏ vs API: cross-encoder OSS chạy trên GPU nội bộ có độ trễ ổn định và không gửi dữ liệu ra ngoài — quan trọng với dữ liệu ngân hàng; API thương mại tiện nhưng phải cân nhắc rò rỉ dữ liệu và độ trễ mạng.
  • Bậc thang mô hình: dùng cross-encoder cho phần lớn truy vấn; chỉ nâng lên LLM-as-reranker cho nhóm câu hỏi khó/giá trị cao. Không cần "đắt nhất cho mọi câu".

Pseudocode: retrieve rộng → rerank → nén → LLM

Đoạn dưới chỉ minh hoạ logic pipeline, không phải API thật của thư viện cụ thể nào. Mô hình sinh câu trả lời trong ví dụ là claude-opus-4-8 (chỉ để minh hoạ):

# MINH HOẠ — không phải API thật của thư viện nào
def answer(query, user_ctx, top_n=100, top_k=5):
    # 1) RETRIEVE rộng (recall): hybrid BM25 + vector, có pre-filter quyền/độ mật
    candidates = hybrid_search(query, filters=user_ctx.filters, top_n=top_n)

    # 2) RERANK (precision): cross-encoder chấm TỪNG cặp (query, doc) trong 1 batch
    pairs   = [(query, doc.text) for doc in candidates]
    scores  = cross_encoder.score(pairs)                 # batch -> 1 điểm/cặp
    ranked  = [doc for doc, _ in
               sorted(zip(candidates, scores),
                      key=lambda z: z[1], reverse=True)]

    # 3) MMR: bớt trùng lặp, giữ đa dạng, lấy top-k
    top = mmr_select(query, ranked, k=top_k, lambda_=0.7)

    # 4) NÉN ngữ cảnh: cắt câu thừa, chỉ giữ phần liên quan tới query
    compressed = [contextual_compress(query, doc.text) for doc in top]

    # 5) Sắp xếp chống "lost in the middle": quan trọng nhất ra đầu & cuối
    ordered = reorder_edges(compressed)

    # 6) LLM sinh câu trả lời từ ngữ cảnh đã tinh lọc
    return llm.generate(model="claude-opus-4-8",
                        query=query, context=ordered)

Điểm cần nhớ: bước 1 nhận top_n lớn (recall), bước 2–3 thu về top_k nhỏ (precision), và reranker chấm cả cặp (query, doc.text) — đó là khác biệt bản chất so với việc chỉ so cosine hai vector đã nhúng sẵn.

Use case thực tế

Bối cảnh — trợ lý tra cứu văn bản nội bộ NCB. Tiếp nối use case ở RAG 3: kho ~12.000 tài liệu (quy trình tín dụng, quyết định, biểu phí, sản phẩm). Sau khi bật hybrid, recall@10 đã tốt (~0,89), nhưng đội nghiệp vụ phản ánh một vấn đề khác: đúng tài liệu có trong top-10, nhưng điều khoản cần thì không nằm ở 2–3 đoạn đầu mà LLM đọc kỹ nhất, nên câu trả lời đôi khi trích sai mục hoặc bỏ sót con số. Đây đúng là bài toán precision ở đỉnh bảng — việc của rerank.

Thiết kế. Giữ hybrid retrieve N = 60 (recall rộng), thêm cross-encoder reranker chạy on-prem (mô hình OSS, không gửi dữ liệu ra ngoài) chấm lại 60 đoạn theo lô, giữ top-5, bật nén ngữ cảnh để cắt phần thừa trước khi vào LLM.

Đo trước/sau trên tập ~200 câu hỏi có nhãn của đội nghiệp vụ, chỉ tiêu là precision@5 (tỉ lệ trong 5 đoạn đưa vào LLM thật sự chứa thông tin trả lời được):

Cấu hìnhprecision@5 (ước lượng)p95 latency thêm
Hybrid, không rerank~0,620 ms (mốc)
Hybrid + cross-encoder rerank (top-5)~0,82+180–350 ms
+ nén ngữ cảnh~0,82 (giữ nguyên)token prompt giảm ~40%

Cải thiện rõ nhất ở nhóm câu hỏi "điều khoản/biểu phí cụ thể" — trước đây đoạn vàng hay tụt xuống hạng 6–9, sau rerank được đẩy lên top-3. Nén ngữ cảnh không làm tăng precision nhưng giảm ~40% token prompt, kéo chi phí và độ trễ sinh của LLM xuống, bù lại phần latency mà rerank thêm vào.

Cân nhắc latency. Rerank thêm ~0,2–0,35s ở p95 — chấp nhận được với trợ lý tra cứu (người dùng chờ được vài trăm ms để đổi lấy câu trả lời đúng). Để giữ mức đó: chạy reranker on-prem trên GPU, chấm theo batch 60 đoạn một lượt, và cache kết quả cho nhóm câu hỏi lặp (FAQ nội bộ chiếm phần lớn lưu lượng). Nếu sau này cần độ chính xác cao hơn cho nhóm câu hỏi pháp lý phức tạp, có thể nâng riêng nhóm đó lên LLM-as-reranker, chấp nhận latency cao hơn cho số ít truy vấn giá trị cao.

(Các con số trên là ước lượng minh hoạ theo bậc độ lớn thường gặp khi bật rerank, không phải số đo chính thức của NCB.)

Bước tiếp theo để đẩy chất lượng còn xa hơn là biến đổi truy vấn trước cả retrieval — viết lại, tách câu hỏi phức, sinh nhiều biến thể — trình bày ở RAG nâng cao 5 — Query Transformation. Toàn cảnh pipeline RAG nâng cao xem ở RAG nâng cao 1 — Tổng quan.

Ghi nhớ

  • Retrieval (bi-encoder/vector) nhúng câu hỏi và tài liệu độc lập → điểm cosine chỉ xấp xỉ độ liên quan: nhanh, rẻ, nhưng thô. Dùng để lọc từ triệu xuống top-N.
  • Cross-encoder rerank đưa cả cặp (câu hỏi, tài liệu) qua model cùng lúc → điểm chính xác hơn nhiều, nhưng không tái sử dụng được, phải chạy lại mỗi cặp → chỉ rerank top-N (N ≈ 50–100), rồi giữ top-k (k ≈ 3–8).
  • Nguyên tắc kiến trúc: retrieve rộng lo recall, rerank hẹp lo precision. N lớn → recall cao nhưng latency/chi phí tăng tuyến tính.
  • Ba nhóm reranker: cross-encoder (mặc định, cân bằng; ví dụ Cohere Rerank, bge-reranker, mxbai-rerank), ColBERT/late-interaction (token-level, nhanh hơn, tốn lưu trữ), LLM-as-reranker (mạnh nhất, đắt/chậm nhất — dành cho N nhỏ, câu giá trị cao).
  • Sau rerank: MMR giảm trùng lặp/đa dạng hoá; nén ngữ cảnh cắt phần thừa (tiết kiệm token, bớt nhiễu); sắp xếp đặt đoạn quan trọng ở đầu/cuối để tránh "lost in the middle".
  • Chi phí/độ trễ: chọn N vừa đủ, batch khi chấm, cache câu hỏi lặp, ưu tiên reranker on-prem cho dữ liệu ngân hàng, và dùng bậc thang mô hình thay vì "đắt nhất cho mọi câu".
  • Chuỗi chuẩn: hybrid retrieve N=50–100 → rerank → top-k=3–8 → nén → LLM; tiếp theo nâng chất bằng query transformation.

Nguồn tham khảo

  • Khattab & Zaharia (2020), "ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT" — SIGIR 2020, arXiv:2004.12832
  • Liu et al. (2023), "Lost in the Middle: How Language Models Use Long Contexts" — arXiv:2307.03172
  • Carbonell & Goldstein (1998), "The Use of MMR, Diversity-Based Reranking for Reordering Documents and Producing Summaries" — SIGIR 1998
  • Sentence-Transformers Documentation — mục "Cross-Encoders" (Retrieve & Re-Rank), sbert.net
  • Cohere Documentation — "Rerank" (docs.cohere.com)
  • MS MARCO Passage Ranking Dataset — microsoft.github.io/msmarco

Bài viết liên quan

Đặt nền cho chuỗi AI: phân biệt ba vòng tròn lồng nhau AI ⊃ ML ⊃ DL và khác biệt bản chất giữa lập trình truyền thống với học từ dữ liệu. Giới thiệu ba kiểu học máy (supervised, unsupervised, reinforcement), phân loại descriptive/predictive/prescriptive, quy trình ML end-to-end, chia train/validation/test, overfitting/underfitting và các thuật ngữ nền tảng, gắn với ứng dụng ngân hàng NCB.

13 thg 7, 2026 18

Harness — lớp scaffolding quanh model (vòng lặp, tool, context, memory, verify, sub-agent) — mới là thứ quyết định agent chạy được hay chỉ là demo. Bài này mổ xẻ giải phẫu một harness, 7 kỹ năng cốt lõi khi xây agent, single vs multi-agent (kèm số liệu hiệu quả/chi phí), các repo nên dùng, và một quickstart Python dựng-là-chạy cho bối cảnh ngân hàng.

13 thg 7, 2026 15

Hiểu LLM từ gốc: bản chất dự đoán token, ba giai đoạn huấn luyện (pretraining, fine-tuning, RLHF), token, context window và các tham số sinh (temperature, top-p). Nắm hiện tượng hallucination và kỹ thuật prompt engineering (vai trò, few-shot, chain-of-thought, ràng buộc đầu ra), kèm ví dụ gọi API model Claude mới nhất với adaptive thinking.

13 thg 7, 2026 14

Vì sao dữ liệu quyết định chất lượng mô hình hơn cả thuật toán. Bài này đi qua toàn bộ pipeline chuẩn bị dữ liệu: phân loại dữ liệu, làm sạch (thiếu/ngoại lai/trùng lặp), mã hoá hạng mục, scaling, feature engineering, giảm chiều, và cách phòng data leakage — soi qua bài toán chấm điểm tín dụng.

13 thg 7, 2026 14

Cảm nhận của bạn

Bình luận

Bạn cần để viết bình luận.

Chưa có bình luận. Hãy là người đầu tiên chia sẻ!