ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Bi-Encoder vs Cross-Encoder, 왜 둘 다 필요한가
    IT 2026. 3. 26. 21:00
    Bi-Encoder vs Cross-Encoder, 왜 둘 다 필요한가

    이 글을 왜 읽어야 하나?

    RAG(Retrieval-Augmented Generation, 검색 증강 생성)를 공부하다 보면 "Bi-Encoder로 후보를 뽑고 Cross-Encoder로 Reranking한다"는 말을 자주 만납니다. 그런데 둘 다 "두 텍스트가 얼마나 관련 있는지" 점수를 매기는 건데, 왜 하나로 안 되고 굳이 두 개가 필요할까요?

    이 질문에 답하려면, 이 두 방식이 어떤 근본적인 문제를 풀기 위해 나왔는지부터 이해해야 합니다. 결론부터 말하면, 둘은 같은 문제의 서로 다른 면을 해결합니다. 그래서 둘 다 필요합니다.

    실생활 비유: 도서관에서 책 찾기

    비유를 하나 들어보겠습니다.

    Bi-Encoder는 도서관의 분류 시스템입니다. 모든 책에 미리 분류 번호를 매겨두고, 당신이 원하는 주제의 번호만 알면 해당 서가로 바로 갑니다. 빠르지만, 같은 서가에 있는 책들 중 정확히 어떤 책이 당신의 질문에 가장 잘 맞는지는 구분하지 못합니다.

    Cross-Encoder는 사서(司書)입니다. 당신의 질문을 듣고, 책 하나하나를 펼쳐서 읽어본 다음 "이 책이 제일 맞습니다"라고 골라줍니다. 정확하지만, 도서관의 모든 책을 다 읽어볼 수는 없습니다.

    그래서 실제로는 분류 시스템으로 서가를 좁히고(Bi-Encoder) → 사서가 그 안에서 최적의 책을 골라주는(Cross-Encoder) 2단계를 씁니다.

    풀어야 했던 핵심 문제: "의미적 유사도"를 어떻게 계산할 것인가

    전통적인 방법의 한계

    2018년 이전의 텍스트 검색은 대부분 키워드 매칭이었습니다. TF-IDF(단어 빈도 기반)나 BM25(키워드 매칭의 개선판) 같은 방식이죠. "강아지 산책 팁"을 검색하면 "강아지", "산책", "팁"이라는 단어가 들어간 문서를 찾습니다.

    하지만 이런 한계가 있었습니다:

    검색어 키워드 매칭이 놓치는 문서 이유
    "강아지 산책 팁" "반려견 외출 시 주의사항" 같은 의미인데 단어가 다름
    "Python 에러 해결" "디버깅 가이드" 같은 맥락인데 키워드 불일치
    "서버가 느려요" "레이턴시 최적화 방법" 일상어 vs 기술 용어

    핵심 문제는 명확합니다: "단어가 달라도 의미가 같으면 찾아야 한다." 이것을 Semantic Search(의미 검색)라고 부르며, 이를 가능하게 한 것이 BERT(2018) 같은 트랜스포머(Transformer) 기반 언어 모델입니다.

    BERT의 등장, 그리고 새로운 문제

    BERT(Bidirectional Encoder Representations from Transformers)는 텍스트의 "의미"를 숫자로 표현할 수 있게 만들었습니다. 하지만 BERT를 직접 유사도 계산에 쓰려면 심각한 문제가 있었습니다.

    BERT로 두 문장의 유사도를 구하려면, 두 문장을 하나로 합쳐서 모델에 넣어야 합니다:

    입력: [CLS] 강아지 산책 팁 [SEP] 반려견 외출 시 주의사항 [SEP]
    출력: 관련도 점수 0.92
    

    정확하지만, 문서가 10,000개라면? 쿼리 하나에 10,000번 BERT를 돌려야 합니다. 문서가 100만 개면 100만 번. 검색 한 번에 몇 시간이 걸립니다. 현실에서 쓸 수 없는 속도입니다.

    이것이 바로 2019년에 Bi-Encoder(Sentence-BERT)가 등장한 배경입니다.

    Bi-Encoder: "미리 계산해두자"는 발상

    "Bi"가 뭔 뜻인가?

    Bi는 "둘(two)"이라는 뜻의 접두사입니다. Bicycle(자전거)의 Bi와 같습니다. Bi-Encoder는 말 그대로 두 개의 Encoder(인코더)를 따로 돌린다는 뜻입니다. 하나는 쿼리를, 다른 하나는 문서를 각각 독립적으로 벡터로 변환합니다. (실제로는 같은 모델을 공유하지만, 두 입력이 서로를 전혀 참조하지 않으므로 "두 개의 독립된 인코딩 경로"라는 의미에서 Bi라는 이름이 붙었습니다.)

    핵심 아이디어

    Bi-Encoder의 핵심 발상은 간단합니다: "문서의 의미를 미리 벡터로 만들어 저장해두면, 검색할 때는 벡터끼리 거리만 비교하면 되지 않을까?"

    diagram

    이 방식의 핵심을 논문(Sentence-BERT, Reimers & Gurevych, 2019)에서 정리하면:

    1. 오프라인 단계: 모든 문서를 각각 Encoder에 넣어 벡터로 변환 → 벡터 DB에 저장
    2. 온라인 단계: 쿼리만 벡터로 변환 → 저장된 벡터들과 cosine similarity 계산

    문서 100만 개가 있어도, 검색 시점에 Encoder를 돌리는 건 쿼리 1번뿐입니다. 나머지는 벡터 간 거리 계산(덧셈, 곱셈)이라 밀리초면 끝납니다.

    속도 비교

    방식 문서 100만 개 검색 시 모델 추론 횟수
    BERT 직접 비교 ~수 시간 1,000,000회
    Bi-Encoder ~수 밀리초 1회 (쿼리만)

    대가: 정확도 손실

    하지만 공짜 점심은 없습니다. Bi-Encoder는 쿼리와 문서를 따로따로 인코딩합니다. 인코딩할 때 상대방의 내용을 전혀 모릅니다. 쿼리 벡터는 "강아지 산책 팁"이라는 문장만 보고 만들어지고, 문서 벡터는 "반려견 외출 시 주의사항"이라는 문장만 보고 만들어집니다.

    이것은 마치 두 사람이 각자 방에서 시험 답안을 쓰고, 나중에 답안지를 비교하는 것과 같습니다. 대략적인 방향은 맞지만, 미묘한 차이를 잡아내기 어렵습니다.

    예를 들어:

    쿼리: "Apple의 최신 제품"
    문서 A: "Apple이 M4 칩을 탑재한 MacBook Pro를 발표했다"  → 관련 있음
    문서 B: "사과(apple)의 영양소와 건강 효과"               → 관련 없음
    

    Bi-Encoder는 "Apple"이라는 단어의 문맥을 쿼리 안에서만 파악하기 때문에, 문서 B에도 높은 점수를 줄 수 있습니다. 쿼리와 문서를 함께 봤다면 "아, 이 Apple은 회사 이름이구나"를 바로 알 수 있었을 텐데요.

    Cross-Encoder: "정확하게 비교하자"는 원점 회귀

    "Cross"가 뭔 뜻인가?

    Cross는 "교차(交叉)"라는 뜻입니다. Cross-Encoder에서는 쿼리의 토큰(단어 조각)과 문서의 토큰이 Transformer의 Self-Attention 안에서 서로 교차하며 정보를 주고받습니다. "강아지"라는 쿼리 토큰이 문서 속 "반려견"이라는 토큰을 직접 바라보고, "이건 나와 같은 뜻이구나"를 파악합니다. Bi-Encoder에서는 이 교차가 불가능했죠 — 각자의 방에서 따로 인코딩하니까요. 이 "교차 참조(cross-attention)"가 가능하다는 것이 핵심이고, 그래서 Cross-Encoder라는 이름이 붙었습니다.

    핵심 아이디어

    Cross-Encoder는 사실 BERT의 원래 방식에 더 가깝습니다. 쿼리와 문서를 하나의 입력으로 합쳐서 모델에 넣고, 관련도를 직접 출력합니다.

    diagram

    쿼리와 문서가 같은 모델 안에서 서로를 "보면서" 처리됩니다. Transformer의 Self-Attention(자기 주의) 메커니즘이 "강아지"와 "반려견"이 같은 의미라는 것을, 그리고 "산책"과 "외출"이 이 맥락에서 관련 있다는 것을 문서의 내용을 직접 참조하면서 판단합니다.

    앞의 Apple 예시에서도, Cross-Encoder는 쿼리("Apple의 최신 제품")와 문서("사과의 영양소")를 함께 읽기 때문에 "이 Apple은 과일이 아니라 회사인데, 이 문서는 과일 이야기네"를 정확히 구분할 수 있습니다.

    대가: 속도

    문제는 처음과 같습니다. 후보가 20개면 모델 추론 20번, 100개면 100번. Bi-Encoder처럼 미리 계산해둘 수 없습니다. 왜냐하면 쿼리가 바뀔 때마다 모든 점수를 다시 계산해야 하기 때문입니다.

    핵심: 왜 둘 다 필요한가

    이제 핵심 질문에 답할 수 있습니다. 두 방식은 같은 문제(의미적 유사도 계산)의 서로 다른 트레이드오프를 선택한 것입니다:

    diagram

    비교 항목 Bi-Encoder Cross-Encoder
    인코딩 방식 쿼리와 문서를 따로 인코딩 쿼리+문서를 함께 인코딩
    출력 각각의 벡터 → 거리 비교 관련도 점수 (0~1)
    사전 계산 가능 (문서 벡터 미리 저장) 불가능 (쿼리마다 재계산)
    속도 (100만 문서) ~밀리초 ~수 시간 (비현실적)
    정확도 좋음 (대략적) 매우 좋음 (정밀)
    RAG에서의 역할 1단계: 후보 추출 (Retrieval) 2단계: 재순위 (Reranking)
    최적화 대상 Recall (관련 문서를 놓치지 않기) Precision (상위 결과의 정확도)
    대표 모델 all-MiniLM-L6, text-embedding-3 ms-marco-MiniLM, bge-reranker

    한마디로 정리하면:

    • Bi-Encoder: "관련 있을 수도 있는 것을 최대한 빠르게, 많이 찾자" (Recall 중심)
    • Cross-Encoder: "진짜 관련 있는 것만 정확하게 골라내자" (Precision 중심)

    Bi-Encoder만 쓰면 빠르지만 부정확한 결과가 LLM에 전달됩니다. Cross-Encoder만 쓰면 정확하지만 현실적인 시간 안에 검색이 끝나지 않습니다. 둘을 조합해야 "빠르면서도 정확한" 검색이 가능합니다.

    RAG 파이프라인에서의 실전 활용

    전형적인 파이프라인

    사용자 질문
      │
      ▼
    [Bi-Encoder] 전체 문서에서 상위 100개 후보 추출  ← 밀리초
      │
      ▼
    [Cross-Encoder] 100개 중 상위 10개 재순위        ← 수백 밀리초
      │
      ▼
    [LLM] 상위 10개 문서를 컨텍스트로 답변 생성       ← 수 초
    

    실전 코드 예시

    from sentence_transformers import SentenceTransformer, CrossEncoder
    
    # ── 1단계: Bi-Encoder로 후보 추출 ──
    bi_encoder = SentenceTransformer("all-MiniLM-L6-v2")
    
    # 문서 벡터는 미리 계산해서 DB에 저장해둔 것
    query = "Kubernetes Pod가 CrashLoopBackOff 상태일 때 해결 방법"
    query_vec = bi_encoder.encode(query)
    
    # 벡터 DB에서 유사한 문서 100개 검색 (cosine similarity)
    candidates = vector_db.search(query_vec, top_k=100)
    # → 약 3ms 소요
    
    # ── 2단계: Cross-Encoder로 Reranking ──
    cross_encoder = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-12-v2")
    
    # (쿼리, 문서) 쌍 생성
    pairs = [(query, doc.text) for doc in candidates]
    
    # Cross-Encoder로 관련도 점수 계산
    scores = cross_encoder.predict(pairs)
    # → 100개 기준 약 200ms 소요
    
    # 점수순 정렬 후 상위 10개 추출
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    top_10 = [doc for doc, score in ranked[:10]]
    
    # ── 3단계: LLM에 전달 ──
    context = "\n\n".join([doc.text for doc in top_10])
    answer = llm.generate(f"Context:\n{context}\n\nQuestion: {query}")
    

    Reranking의 효과: 실험 결과

    Bi-Encoder만 사용했을 때와 Cross-Encoder Reranking을 추가했을 때의 차이는 학계와 실무 모두에서 일관되게 보고되고 있습니다:

    평가 지표 Bi-Encoder만 Bi + Cross (Reranking) 개선
    MRR@10 (MS MARCO) 0.33 0.39 +18%
    NDCG@10 (BEIR 벤치마크 평균) 0.44 0.52 +18%
    Top-1 정확도 (실무 RAG) ~60% ~75% +25%

    특히 Top-1 정확도(첫 번째 결과가 정답인 비율)에서 차이가 큽니다. RAG에서 LLM에 전달되는 첫 번째 문서가 정확할수록 답변 품질이 올라가기 때문에, Reranking의 가치가 여기서 극대화됩니다.

    현재 얼마나 널리 쓰이고 있나?

    결론부터 말하면, Bi-Encoder + Cross-Encoder 조합은 현재 RAG의 사실상 표준(de facto standard)입니다.

    업계 채택 현황

    영역 사용 사례 비고
    검색 엔진 Google, Bing 모두 2단계 retrieval + reranking 파이프라인 사용 웹 검색의 기본 아키텍처
    RAG 프레임워크 LangChain, LlamaIndex 모두 Reranker를 1급 컴포넌트로 제공 기본 파이프라인 템플릿에 포함
    클라우드 서비스 Cohere Rerank API, Jina Reranker, Voyage Reranker Reranking 전용 API 서비스 등장
    벡터 DB Qdrant, Weaviate 등이 하이브리드 검색 + reranking 통합 지원 DB 레벨에서 직접 지원
    오픈소스 모델 BGE-Reranker, ms-marco 시리즈, Cohere multilingual 등 한국어 지원 모델도 다수

    최근 트렌드

    이 분야는 계속 진화하고 있습니다:

    • Late Interaction 모델 (ColBERT): Bi-Encoder의 속도와 Cross-Encoder의 정확도 사이의 절충점. 토큰 단위 벡터를 저장해서 더 세밀한 매칭을 하되, 사전 계산이 가능합니다. "Bi와 Cross의 중간 지점"이라고 볼 수 있습니다.
    • LLM 기반 Reranking: GPT-4나 Claude 같은 LLM에게 직접 "이 문서들 중 질문에 가장 관련 있는 것을 골라줘"라고 요청하는 방식. Cross-Encoder보다 정확할 수 있지만, 비용과 지연 시간이 큽니다.
    • Learned Sparse Retrieval (SPLADE): 키워드 매칭의 장점과 의미 검색의 장점을 결합한 하이브리드 방식. Bi-Encoder를 보완하거나 대체할 수 있습니다.

    하지만 이 모든 새로운 접근들도 "빠르게 넓게 찾고(retrieval) → 정확하게 좁히는(reranking)" 2단계 구조 자체를 바꾸지는 않습니다. 각 단계에 어떤 모델을 쓰느냐가 달라질 뿐, Bi-Encoder와 Cross-Encoder가 정립한 이 패턴은 여전히 핵심 설계 원칙으로 남아 있습니다.

    정리

    • 풀어야 한 문제: "단어가 달라도 의미가 같으면 찾아야 한다" (Semantic Search)
    • Bi-Encoder의 해법: 텍스트를 미리 벡터로 만들어두고 거리만 비교 → 빠르지만 대략적
    • Cross-Encoder의 해법: 쿼리와 문서를 함께 읽어서 관련도를 직접 계산 → 정확하지만 느림
    • 트레이드오프: 속도 vs 정확도. 하나만으로는 부족하고 둘을 조합해야 함
    • RAG에서의 활용: Bi-Encoder(retrieval) → Cross-Encoder(reranking) → LLM(생성)
    • 현재 위치: 이 2단계 구조는 RAG의 사실상 표준이며, 주요 프레임워크와 클라우드 서비스에서 기본 지원

    "왜 두 개가 필요한가?"에 대한 답은 간단합니다. 수백만 개 문서를 전부 정밀하게 읽을 수는 없고, 대충 훑어서 후보를 좁힌 다음 정밀하게 읽는 게 현실적인 유일한 방법이기 때문입니다. 사람이 도서관에서 책을 찾는 방식과 똑같습니다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.