Field Log · Entry

Reranking: Cross-Encoder·monoT5·ColBERT의 역할 (7/10)

Bi-Encoder 후보 생성 뒤 Cross-Encoder joint attention, monoT5 relevance token, ColBERT MaxSim으로 후보를 재정렬하는 비교

오늘의 결론

  • Retriever는 corpus에서 놓치지 않고 후보를 모으고, reranker는 작은 후보 안에서 정확한 순서를 만듭니다.
  • Cross-Encoder는 query와 document token을 함께 attention해 세밀하지만 문서 vector를 미리 계산할 수 없습니다.
  • monoT5는 query-document relevance를 text token 확률로 표현하는 reranker입니다.
  • ColBERT는 document token vector를 미리 저장하고 query token별 MaxSim을 합치는 중간 지점입니다.
  • reranker는 candidate set에 없는 정답을 복구하지 못하므로 후보 Recall을 먼저 확인합니다.

앞 글의 hybrid search는 BM25와 dense 결과를 합쳐 넓은 후보를 만들었습니다. 그러나 E104 인증 만료E104 연결 시간 초과처럼 비슷한 후보의 세부 조건을 1차 score만으로 구분하기 어려울 수 있습니다.

Reranking은 top-50이나 top-100 같은 작은 후보 집합을 더 비싼 model로 다시 점수화합니다.

Retriever 후보 생성과 Cross-Encoder, monoT5, ColBERT reranking 비교

그림 1. Bi-Encoder는 passage vector를 재사용해 넓게 찾고, Cross-Encoder는 query-document를 함께 읽으며, ColBERT는 사전 계산한 token vector와 MaxSim으로 두 극단 사이를 잇는다.


두 단계 검색의 역할 분담

Stage 1 · Candidate retrieval
BM25 / Dense ANN / Hybrid
corpus 10,000,000개 → candidate 100개
목표: 높은 Recall@100

Stage 2 · Reranking
Cross-Encoder / monoT5 / ColBERT
candidate 100개 → final 5개
목표: 높은 MRR·nDCG@5·Precision@5

첫 단계가 gold passage를 놓치면 두 번째 단계는 아무것도 할 수 없습니다.

candidate contains gold = false
→ reranker maximum success = false

따라서 reranker 평가 report에는 반드시 candidate set recall을 함께 씁니다.

Bi-Encoder가 놓치는 interaction

Bi-Encoder는 query와 passage를 별도로 한 vector에 압축합니다.

score(q, d) = E_Q(q) · E_D(d)

빠르지만 query의 E104가 document의 어느 token과 대응하고, 연결 시간 초과인증 만료와 어떻게 다른지 joint attention으로 직접 비교하지 않습니다.

한 vector에 다음 세부를 모두 보존해야 합니다.

  • exact identifier
  • 숫자와 단위
  • 부정과 조건
  • entity 관계
  • 문장 내 위치
  • query가 강조한 부분

Reranker는 후보가 작아진 뒤 더 많은 interaction을 계산합니다.

Cross-Encoder: query와 document를 함께 읽는다

입력은 대략 다음처럼 구성됩니다.

[CLS] query tokens [SEP] document tokens [SEP]

Transformer self-attention에서 query token과 document token이 모든 layer에서 상호작용합니다.

"E104" ↔ document의 "E104"
"연결" ↔ "네트워크"
"시간 초과" ↔ "timeout"

마지막 representation에서 relevance logit 또는 score를 예측합니다.

장점

  • exact term과 문맥 관계를 함께 볼 수 있습니다.
  • 부정, 숫자, 조건의 mismatch를 구분할 가능성이 큽니다.
  • top-k ranking 품질이 Bi-Encoder보다 높은 경우가 많습니다.

비용

  • query-document pair마다 Transformer forward가 필요합니다.
  • document representation을 query와 독립적으로 precompute할 수 없습니다.
  • candidate 수와 sequence length가 latency·GPU memory를 직접 늘립니다.

corpus 1천만 개 전체에 적용하지 않고 1차 검색 후보에만 적용하는 이유입니다.

Cross-Encoder를 Python으로 붙인다

Sentence Transformers의 CrossEncoder를 사용하는 기본 구조입니다. model ID는 언어·domain·license·학습 task에 맞춰 선택합니다.

from dataclasses import dataclass
import numpy as np
from sentence_transformers import CrossEncoder

@dataclass(frozen=True)
class Candidate:
    chunk_id: str
    text: str
    retrieval_rank: int
    retrieval_score: float
    source: str

@dataclass(frozen=True)
class RerankedHit:
    candidate: Candidate
    rerank_score: float
    rerank_rank: int

model = CrossEncoder("YOUR_MULTILINGUAL_OR_DOMAIN_RERANKER")

def rerank(
    query: str,
    candidates: list[Candidate],
    top_k: int,
) -> list[RerankedHit]:
    if not 1 <= top_k <= len(candidates):
        raise ValueError("top_k is outside candidate size")

    pairs = [(query, candidate.text) for candidate in candidates]
    scores = np.asarray(
        model.predict(pairs, batch_size=32, show_progress_bar=False),
        dtype=np.float32,
    ).reshape(-1)

    order = np.argsort(-scores, kind="stable")[:top_k]
    return [
        RerankedHit(
            candidate=candidates[index],
            rerank_score=float(scores[index]),
            rerank_rank=rank,
        )
        for rank, index in enumerate(order, start=1)
    ]

동점일 때 stable sort가 원래 candidate order를 보존합니다. 더 명시적으로 (-score, retrieval_rank, chunk_id)를 tie-break로 사용할 수도 있습니다.

Raw logit을 확률로 착각하지 않는다

Cross-Encoder output이 3.2라고 320% relevance가 아닙니다. model에 따라 logit, regression score, class probability가 다릅니다.

ranking만 필요하면 score 순서를 사용하고, threshold나 “relevant 확률”로 쓰려면 별도 calibration을 평가합니다.

monoBERT에서 monoT5로

초기 BERT reranker는 query-passage pair를 BERT에 넣어 relevance score를 예측했습니다. 이를 흔히 monoBERT 계열로 부릅니다.

monoT5는 sequence-to-sequence model인 T5에 ranking을 text-to-text task로 표현했습니다.

Input:
Query: 계정 복구 방법
Document: 비밀번호 재설정과 인증 복구 절차
Relevant:

Target token:
true

truefalse token의 logit으로 relevance를 계산할 수 있습니다.

P(true | query, document)

monoT5의 의미

  • pretrained seq2seq model을 ranking task로 fine-tuning
  • query-document를 함께 입력해 full interaction 사용
  • output vocabulary의 relevance token score로 ranking

Cross-Encoder라는 넓은 범주 안에서 encoder-only classification과 encoder-decoder text generation 방식이 다른 것입니다.

비용 주의

reranking에 자유로운 긴 답변 generation이 필요한 것은 아닙니다. relevance token logit만 계산하도록 구현할 수 있습니다. framework가 실제로 몇 token을 decode하는지 확인합니다.

ColBERT: token-level late interaction

ColBERT는 query와 document를 별도로 encoding하지만 하나의 vector로 평균내지 않고 token vector를 유지합니다.

Q = [q₁, q₂, ..., qₘ]
D = [d₁, d₂, ..., dₙ]

각 query token이 document token 중 가장 비슷한 하나를 찾고 그 값을 합칩니다.

MaxSim(qᵢ, D) = maxⱼ qᵢ · dⱼ

score(Q,D) = Σᵢ maxⱼ qᵢ · dⱼ

예를 들어 query token E104, 연결, 초과가 document 안의 서로 다른 token과 각각 match할 수 있습니다.

왜 late interaction인가

  • document token vector는 query 없이 미리 계산 가능
  • query가 들어온 뒤 token 간 MaxSim interaction 수행
  • single-vector Bi-Encoder보다 세밀함
  • full Cross-Encoder보다 interaction 시점이 늦고 제한적

비용

  • document당 vector 하나가 아니라 token 수만큼 저장
  • index와 retrieval 구현이 복잡
  • MaxSim 계산과 candidate generation 최적화 필요
  • token pruning·compression에 따라 품질과 저장량 trade-off

블로그의 ColBERT Late Interaction 해설에는 실제 token matrix와 MaxSim 수치 예제가 더 자세히 정리돼 있습니다.

세 구조를 같은 축으로 비교한다

방식document 사전 계산interaction검색 범위대표 비용
Bi-Encodersingle vectordot/cosinecorpus 전체vector index
ColBERTtoken vectorsquery-token MaxSimcorpus 또는 후보큰 token index
Cross-Encoder불가full joint attention작은 후보pair forward
monoT5불가encoder-decoder attention작은 후보pair forward + token logits

실무 pipeline 예시는 다음과 같습니다.

BM25 + Dense ANN top-100 each
→ RRF top-50
→ Cross-Encoder top-10
→ parent expansion / context selection top-5

또는 ColBERT를 1차·중간 retrieval로 사용하고 작은 후보만 Cross-Encoder에 보낼 수 있습니다.

Candidate depth가 비용과 상한을 정한다

rerank candidate 수를 N, pair당 평균 비용을 c라고 하면 계산량은 대략 N × c입니다. batch가 hardware 효율을 높이지만 total token work는 늘어납니다.

N ∈ {10, 20, 50, 100, 200}

각 N에서 측정합니다.

  • candidate Recall@N
  • reranked MRR/nDCG@k
  • p50/p95 latency
  • tokens 또는 padded tokens
  • GPU memory와 throughput

candidate Recall이 20에서 이미 포화되고 nDCG가 개선되지 않으면 100개를 rerank할 이유가 없습니다.

Batch size는 pair 수가 아니라 token 수로 본다

다음 두 batch는 pair 수가 같아도 비용이 다릅니다.

Batch A: 32 pairs × 128 tokens
Batch B: 32 pairs × 512 tokens

padding 때문에 batch의 가장 긴 sequence가 나머지 pair 비용을 늘릴 수 있습니다.

개선 방법

  • passage token length로 candidate를 bucket
  • dynamic batching
  • max sequence length 명시
  • 긴 passage 비율 관찰
  • query별 deadline과 maximum candidate 수

여러 사용자의 pair를 하나의 batch로 합칠 때 결과 mapping과 tenant isolation을 철저히 유지합니다.

Truncation은 reranker의 조용한 실패다

query와 document를 함께 넣으므로 total length가 model limit을 넘을 수 있습니다.

[query tokens] + [special tokens] + [document tokens] ≤ max_length

document 뒤에 정답이 있는데 head truncation만 쓰면 reranker가 근거를 보지 못합니다.

선택지:

  • chunk 자체를 model limit보다 작게 유지
  • heading·핵심 span을 앞에 배치
  • 긴 parent가 아니라 retrieval child를 rerank
  • sliding window로 여러 score를 만들고 max/aggregate
  • query-aware passage extraction 후 rerank

truncated candidate 비율과 정답 span 손실률을 기록합니다.

Reranker는 어떤 negative로 학습할까

random negative보다 현재 retriever가 실제로 올리는 hard negative가 중요합니다.

training loop
1. production-like BM25/dense/hybrid로 top-N 생성
2. gold가 아닌 높은 후보 수집
3. false negative 제거
4. query-positive-negative로 reranker 학습
5. 새 reranker로 holdout 평가

retriever를 교체하면 hard negative 분포도 달라집니다. reranker training data에는 후보를 만든 retriever version을 남깁니다.

Pointwise, pairwise, listwise

목표학습 단위질문
pointwise(q,d) → relevance이 문서는 관련 있는가?
pairwise(q,d⁺,d⁻)어느 문서가 더 관련 있는가?
listwise(q,[d₁...dₙ])전체 순서를 어떻게 둘까?

serving interface가 pointwise score여도 training objective는 pairwise/listwise일 수 있습니다. model 문서를 확인합니다.

Reranking metric을 읽는 법

Reranker는 후보를 추가하지 않고 순서만 바꾸므로, candidate set이 고정되면 Recall@N은 그대로입니다. 대신 작은 cutoff의 rank-sensitive metric이 중요합니다.

MRR

첫 relevant passage가 몇 위에 있는지 봅니다.

RR = 1 / first_relevant_rank
MRR = query별 RR 평균

첫 정답이 rank 10에서 rank 2로 오르면 0.1 → 0.5입니다.

nDCG@k

여러 relevance grade와 순위 discount를 반영합니다. 매우 관련 있음, 부분 관련 있음 같은 graded label에 적합합니다.

Precision@k와 context precision

LLM에 보내는 top-5 중 실제로 유용한 근거 비율을 봅니다. candidate Recall이 같아도 context noise를 줄일 수 있습니다.

올바른 비교표

Run A: Hybrid top-50 그대로
Run B: Hybrid top-50 → Cross-Encoder
Run C: Hybrid top-50 → monoT5
Run D: ColBERT top-50 → Cross-Encoder

각 run에 다음을 기록합니다.

단계metric
후보Recall@50, unique sources
순위MRR, nDCG@5/10, Precision@5
생성faithfulness, answer correctness
성능p50/p95, pairs/sec, tokens/sec
비용GPU memory, API cost

후보 집합이 다른 B와 D를 비교하면서 개선을 전부 reranker 덕분이라고 해석하지 않습니다.

Rerank trace

{
  "question_id": "q-104",
  "candidate_count": 50,
  "candidate_recall": true,
  "reranker": "cross-encoder-domain-v4",
  "max_length": 512,
  "batch_size": 32,
  "truncated_candidates": 3,
  "hits": [
    {
      "chunk_id": "chunk-e104-timeout",
      "retrieval_rank": 7,
      "retrieval_sources": {"bm25": 2, "dense": 18},
      "rerank_rank": 1,
      "rerank_score": 5.83
    }
  ],
  "latency_ms": 41
}

score만 저장하지 말고 후보가 어디서 왔고 얼마나 이동했는지 기록합니다.

외부 rerank API를 쓸 때

query와 document text가 외부 service로 전송됩니다. 다음을 확인합니다.

  • 개인정보와 영업 비밀 전송 정책
  • data retention과 model training 사용 여부
  • region과 encryption
  • tenant별 API key와 quota
  • timeout·retry·partial failure
  • model version 고정 가능 여부

retrieval에서 ACL을 통과했더라도 외부 전송 허용 여부는 별도 정책일 수 있습니다.

LLM을 reranker로 쓰는 경우

instruction LLM에 후보 list를 주고 순위를 요청할 수도 있습니다. pairwise 또는 listwise prompt가 가능합니다.

장점:

  • 복잡한 relevance 기준을 natural language로 설명 가능
  • label이 적을 때 prototype이 쉬움

주의점:

  • 높은 token·latency 비용
  • 후보 순서와 position bias
  • 출력 parsing 실패와 비결정성
  • document prompt injection
  • 긴 list에서 비교 일관성 저하

먼저 학습된 reranker baseline을 만들고 LLM reranker는 별도 evaluation과 안전 경계로 다룹니다.

Production 체크리스트

  • candidate Recall@N이 reranker 상한으로 기록된다.
  • retrieval과 reranking metric을 분리한다.
  • candidate depth sweep으로 품질·latency 지점을 찾았다.
  • Cross-Encoder output의 의미와 score 방향을 확인했다.
  • max length와 truncation 비율을 기록한다.
  • token length bucket과 batch policy가 있다.
  • reranker hard negative가 production retriever를 반영한다.
  • MRR·nDCG·Precision과 p95를 함께 본다.
  • retrieval rank, rerank rank, model version을 trace한다.
  • 외부 API의 data policy와 timeout fallback을 검증했다.

스스로 확인하기

  1. Cross-Encoder가 document vector를 offline에 미리 계산할 수 없는 이유는 무엇인가?
  2. candidate Recall@50이 0인 query를 reranker가 고칠 수 없는 이유는 무엇인가?
  3. ColBERT의 MaxSim은 single-vector dot product와 어떻게 다른가?
  4. reranker를 붙였는데 Recall@50은 같고 nDCG@5가 오르는 것이 자연스러운 이유는 무엇인가?
  5. candidate 수를 늘릴 때 latency 외에 어떤 truncation과 batch 비용을 봐야 하는가?

다음 글에서는 원 질문 자체가 검색에 불리할 때 Rewrite·Expansion·HyDE·Decomposition으로 Query를 변환하는 방법을 다룹니다.

참고자료