RAG 리랭킹 가이드, 검색 결과 정확도 높이는 법
벡터 검색 결과, 왜 상위 순위가 엉뚱할까
RAG 시스템을 직접 구축해서 운영해 본 개발자라면, 벡터 검색 결과의 순위에 한 번쯤은 고개를 갸웃거린 경험이 있을 것입니다. 데이터베이스에 수천 건의 문서를 넣고 의미 검색을 돌리면 관련 있는 문서들이 나오기는 합니다. 그런데 관련 있는 문서가 반환되는 것과, 정확히 필요한 문서가 검색 결과 상위 1순위에 자리잡고 있는 것은 전혀 다른 이야기입니다. 이 사이에 존재하는 품질 격차 때문에 많은 RAG 시스템이 기대 이하의 답변을 생성합니다.
구체적인 시나리오를 하나 떠올려 보겠습니다. 회사 기술 문서 3,000건을 인덱싱한 RAG 시스템에서 누군가 이런 질문을 합니다. 쿠버네티스 Pod가 CrashLoopBackOff 상태일 때 어떻게 해결하나요. 벡터 검색이 반환한 상위 5건을 확인하면 쿠버네티스 관련 문서가 대부분이긴 합니다. 하지만 1순위는 쿠버네티스 클러스터 아키텍처 개요, 2순위는 Pod 네트워크 구성 가이드, 3순위는 컨테이너 이미지 빌드 모범 사례입니다. CrashLoopBackOff 트러블슈팅을 직접 다루는 문서는 4순위에 묻혀 있습니다. 사용자가 원하는 정보는 분명히 데이터베이스에 있는데, 검색 순위가 그 문서를 적절히 올려주지 못하고 있는 것입니다.
이 문제는 우연이 아니라 임베딩 기반 검색의 구조적 특성에서 비롯됩니다. 대부분의 벡터 검색에서 쓰이는 Bi-encoder 임베딩 모델은 질문과 문서를 각각 독립적으로 벡터화합니다. 쿠버네티스라는 큰 주제는 벡터 공간에서 잘 묶이지만, CrashLoopBackOff라는 특정 에러의 해결법이라는 세부 의도까지 하나의 벡터로 정밀하게 구분하기란 태생적으로 어렵습니다. 비슷한 주제 영역의 문서들이 뭉뚱그려 유사한 점수를 받게 되는 것이죠.
RAG에서 LLM이 정확한 답변을 생성하려면, 컨텍스트 윈도우에 들어가는 상위 문서들의 품질이 결정적입니다. 상위 3개 문서가 질문의 핵심을 빗겨가면 아무리 뛰어난 언어 모델이라도 제대로 된 답을 만들어 내기 어렵습니다. 결국 RAG 전체의 성패는 검색 정밀도에 달려 있습니다.
실제로 많은 RAG 시스템이 벡터 검색 결과를 그대로 LLM에 넘기고 있습니다. 임베딩 모델을 더 좋은 것으로 바꾸거나, 청킹 전략을 조정하거나, 프롬프트를 다듬는 데 집중하면서 정작 가장 적은 노력으로 가장 큰 효과를 낼 수 있는 개선 포인트를 간과하는 경우가 많습니다. 바로 리랭킹(Reranking)입니다.
리랭킹은 1차 검색으로 가져온 후보 문서들을 더 정교한 모델로 다시 평가해 순서를 재배열하는 기술입니다. 첫 번째 검색이 넓은 그물로 후보를 끌어 모으는 단계라면, 리랭킹은 그 안에서 진짜 보석을 골라내는 정밀 선별 단계입니다. 이 한 단계를 추가하는 것만으로 검색 정밀도가 20에서 40퍼센트 향상되는 것이 실무에서 반복적으로 확인됩니다. 기존 파이프라인을 전혀 건드리지 않고 중간에 한 단계만 끼워 넣는 것이므로 도입 장벽도 낮습니다.
이 글에서는 리랭킹이 왜 이토록 효과적인지 원리를 파헤치고, 2026년 현재 사용 가능한 리랭킹 모델들을 비교하며, 실제 파이프라인에 통합하는 방법, 그리고 운영 환경에서의 성능 최적화 전략까지 한 번에 정리하겠습니다.
리랭킹의 작동 원리: 두 번 검색하는 이유
리랭킹을 이해하려면 먼저 현재 대부분의 RAG 시스템이 사용하는 검색 방식의 한계를 정확히 파악해야 합니다. 핵심은 Bi-encoder와 Cross-encoder라는 두 가지 인코딩 방식의 근본적 차이에 있습니다. 이 차이를 이해하면 리랭킹이 왜 효과적인지 자연스럽게 납득할 수 있습니다.
Bi-encoder: 빠르지만 얕은 이해
Bi-encoder는 질문과 문서를 각각 별개의 경로로 인코딩합니다. 질문은 질문대로 하나의 벡터(보통 384에서 1536차원)로, 문서는 문서대로 하나의 벡터로 변환합니다. 두 벡터가 만들어진 뒤에야 코사인 유사도나 내적을 통해 관련성을 계산합니다. 이 방식의 핵심 장점은 압도적인 효율성입니다. 문서 벡터는 인덱싱 시점에 미리 계산해 놓을 수 있으므로, 검색 시에는 질문 벡터 하나만 만들면 됩니다. HNSW나 IVF 같은 근사 최근접 이웃(ANN) 알고리즘과 결합하면 수백만 건의 문서에서도 수 밀리초 안에 결과를 가져올 수 있습니다.
하지만 이 효율성에는 대가가 있습니다. 질문과 문서가 서로를 보지 못한 채 각각 벡터로 압축되기 때문에, 둘 사이의 미세한 상호작용을 포착하지 못합니다. 파이썬에서 대용량 CSV 파일을 빠르게 읽는 방법이라는 질문이 있다고 합시다. 후보 문서 A는 pandas read_csv의 chunksize 파라미터 활용법을 다루고, 문서 B는 파이썬 파일 입출력 기초를 설명합니다. Bi-encoder는 둘 다 파이썬과 파일 처리라는 주제를 공유하므로 비슷한 점수를 줄 수 있습니다. 하지만 사용자가 원하는 것은 대용량 처리에 특화된 문서 A이지, 기초적인 open/read 함수를 다루는 문서 B가 아닙니다.
Bi-encoder의 또 다른 약점은 검색 결과 상위권에서의 변별력 부족입니다. 코사인 유사도 0.85와 0.82의 차이가 실제 관련성의 차이를 정확히 반영하지 못하는 경우가 빈번합니다. 비슷한 주제의 문서 수십 개가 0.80에서 0.88 사이의 좁은 점수 대역에 모여 있으면, 이 구간 안에서의 순위가 사실상 무작위에 가까워질 수 있습니다. 사용자에게 보여지는 상위 3개에서 5개 문서의 구성이 질문의 미세한 표현 변화에 따라 들쭉날쭉해지는 원인이 바로 이것입니다.
Cross-encoder: 느리지만 깊은 이해
Cross-encoder는 근본적으로 다른 접근을 취합니다. 질문과 문서를 하나의 입력 시퀀스로 연결해서 트랜스포머 모델에 함께 넣습니다. 모델은 질문의 모든 토큰과 문서의 모든 토큰 사이의 셀프 어텐션을 양방향으로 계산합니다. 질문에서 빠르게라는 단어가 문서 전체 문맥 안에서 어떤 의미와 연결되는지, 문서의 chunksize가 질문이 물어보는 대용량 처리라는 요구와 직접 대응하는지를 토큰 수준에서 교차 분석합니다.
이 양방향 어텐션 덕분에 Cross-encoder는 질문과 문서 사이의 의미적 관계를 훨씬 깊이 있게 파악합니다. 앞의 CSV 예시에서 Cross-encoder는 질문의 대용량이라는 키워드와 문서 A의 chunksize가 직접 연결된다는 것을 어텐션 가중치로 포착할 수 있습니다. 문서 B의 기초적인 파일 입출력은 대용량 처리와 거리가 멀다는 판단도 가능합니다. 학술 벤치마크인 MS MARCO와 BEIR에서 Cross-encoder는 Bi-encoder 대비 NDCG@10 기준으로 10에서 30포인트 높은 점수를 일관되게 기록합니다.
그렇다면 왜 처음부터 Cross-encoder만 쓰지 않을까요. 답은 속도입니다. Cross-encoder는 모든 질문-문서 쌍에 대해 개별적으로 추론을 실행해야 합니다. 데이터베이스에 100만 건의 문서가 있다면, 질문 하나에 대해 100만 번의 추론을 돌려야 합니다. Bi-encoder가 밀리초 단위라면, Cross-encoder로 전수 검색을 하면 수 시간이 걸릴 수 있습니다. 프로덕션 환경에서는 도저히 허용할 수 없는 지연시간입니다.
Retrieve, Rerank, Generate: 최적의 조합
이 딜레마를 해결하는 것이 2단계 검색(Two-stage Retrieval) 패러다임입니다. 빠르지만 거친 필터로 후보를 좁힌 뒤, 느리지만 정밀한 필터로 순서를 가다듬는 것입니다. 전체 파이프라인은 세 단계로 구성됩니다.
첫 번째는 Retrieve(검색) 단계입니다. Bi-encoder 기반 벡터 검색이나 BM25, 또는 하이브리드 검색으로 전체 문서 풀에서 상위 K개의 후보를 빠르게 가져옵니다. 보통 K는 20에서 100 사이로 설정합니다. 이 단계의 목표는 관련 있을 법한 문서를 놓치지 않고 넓게 수집하는 것이며, 순위의 정밀도보다는 재현율(Recall)에 초점을 맞춥니다. 정답 문서가 후보 목록 안에 들어와 있기만 하면 됩니다.
두 번째는 Rerank(리랭킹) 단계입니다. 첫 번째 단계에서 가져온 K개의 후보 문서 각각을 질문과 쌍으로 묶어 Cross-encoder에 통과시킵니다. 각 문서에 대해 질문과의 관련성 점수를 새로 매기고, 이 점수를 기준으로 순서를 재배열합니다. K가 20에서 100개 수준이므로 Cross-encoder를 20에서 100번만 호출하면 됩니다. 전수 검색 대비 수만 분의 일 비용으로 Cross-encoder의 정밀도를 확보하는 핵심 전략입니다.
세 번째는 Generate(생성) 단계입니다. 리랭킹으로 재배열된 상위 N개(보통 3에서 5개) 문서를 LLM의 컨텍스트 윈도우에 넣고 답변을 생성합니다. 리랭킹 덕분에 LLM이 받는 문서의 품질이 크게 향상되므로, 같은 모델을 쓰더라도 답변의 정확도와 관련성이 눈에 띄게 좋아집니다.
이 구조의 핵심 통찰은 각 단계가 자기 강점에 집중한다는 것입니다. Bi-encoder는 속도를, Cross-encoder는 정밀도를 담당합니다. 데이터베이스에서 인덱스 스캔으로 후보를 좁힌 뒤 상세 조건으로 재필터링하는 원리와 같습니다. 실제로 여러 벤치마크에서 이 2단계 구조는 Bi-encoder 단독 검색 대비 NDCG@10을 15에서 25포인트 끌어올리면서도, 전체 응답 지연시간은 수백 밀리초 수준으로 유지하는 것이 확인됩니다.

리랭킹 모델 종류와 선택 가이드
2026년 현재 리랭킹에 사용할 수 있는 모델과 서비스는 크게 네 가지 계열로 나뉩니다. 프로젝트의 규모, 예산, 보안 요구사항, 지원 언어에 따라 최적의 선택이 달라지므로 각 계열의 특성을 정확히 이해하는 것이 중요합니다.
Cross-encoder 전용 모델
가장 전통적이고 널리 검증된 방식입니다. MS MARCO 데이터셋으로 학습된 Cross-encoder 모델들이 대표적입니다. cross-encoder/ms-marco-MiniLM-L-6-v2는 약 80MB의 가벼운 모델이면서도 뛰어난 성능을 보여 입문용과 경량 프로덕션에 모두 적합합니다. 더 높은 정확도가 필요하면 같은 시리즈의 L-12 버전(약 130MB)을 고려할 수 있습니다. 모델 크기가 커질수록 정확도는 올라가지만 추론 시간도 비례해서 증가하므로 트레이드오프를 따져야 합니다.
BAAI(Beijing Academy of Artificial Intelligence)에서 공개한 bge-reranker 시리즈도 주목할 만합니다. bge-reranker-v2-m3는 100개 이상의 언어를 지원하는 다국어 모델로, 영어뿐 아니라 한국어 문서에서도 합리적인 성능을 보입니다. bge-reranker-v2-gemma는 Gemma 아키텍처 기반으로 모델 용량을 키워 최상위 정확도를 추구하는 모델입니다. 모델 크기가 2GB를 넘으므로 GPU가 사실상 필수이지만, 정확도가 최우선인 시나리오에서는 충분히 고려할 가치가 있습니다.
Cross-encoder 전용 모델의 가장 큰 강점은 크기 대비 성능이 뛰어나고, 경량 모델의 경우 GPU 없이 CPU만으로도 합리적인 속도로 동작한다는 점입니다. 또한 Hugging Face에서 바로 다운로드해서 로컬에서 실행할 수 있으므로 데이터가 외부로 나가지 않아 보안 측면에서도 유리합니다.
Late Interaction 모델: ColBERT 계열
ColBERT(Contextualized Late Interaction over BERT)는 Bi-encoder와 Cross-encoder의 장점을 결합한 독특한 아키텍처입니다. 질문과 문서를 각각 토큰 수준의 벡터 집합으로 인코딩하되, 검색 시점에 이 벡터들 사이의 상호작용을 계산합니다. 구체적으로는 질문의 각 토큰 벡터와 문서의 모든 토큰 벡터 사이에서 최대 유사도(MaxSim)를 구한 뒤 합산하는 방식입니다. 이 지연된 상호작용(Late Interaction) 덕분에 Cross-encoder에 근접하는 정밀도를 유지하면서도, 문서 토큰 벡터를 미리 계산해 둘 수 있어 순수 Cross-encoder보다 상당히 빠릅니다.
ColBERT v2와 이를 개선한 후속 모델들, 그리고 PLAID 인덱싱 최적화까지 거친 최신 버전들은 실용적인 속도와 높은 정밀도를 모두 제공합니다. RAGatouille이나 Stanford의 ColBERTv2 라이브러리를 사용하면 파이썬 코드 몇 줄로 ColBERT 리랭킹을 파이프라인에 통합할 수 있습니다. 다만 ColBERT는 토큰 수준 벡터를 모두 저장해야 하므로 인덱스 용량이 일반 Bi-encoder보다 10배에서 50배 커질 수 있습니다. 문서 수가 수십만 건을 넘어가면 스토리지 비용을 신중하게 따져봐야 합니다.
LLM 기반 리랭킹
GPT 시리즈, Claude, Gemini 같은 대규모 언어 모델을 리랭커로 활용하는 방식입니다. 후보 문서 목록과 질문을 LLM에 함께 넘기면서 문서들의 관련성 순위를 매겨 달라고 프롬프트하는 것입니다. 별도의 리랭킹 모델을 학습하거나 호스팅할 필요 없이, 이미 사용 중인 LLM API를 그대로 활용할 수 있어 진입 장벽이 매우 낮습니다.
LLM 기반 리랭킹에는 크게 두 가지 접근이 있습니다. 첫째는 pointwise 방식으로, 각 문서에 대해 개별적으로 관련성 점수(예: 1점에서 10점)를 매기는 것입니다. 둘째는 listwise 방식으로, 후보 문서 전체를 한 번에 넘기고 관련성 순서대로 정렬해 달라고 요청하는 것입니다. RankGPT 논문에서 제안된 listwise 방식은 여러 벤치마크에서 전용 Cross-encoder를 상회하는 성능을 보여 주었습니다.
하지만 LLM 기반 리랭킹의 단점은 뚜렷합니다. 문서 하나당 수백에서 수천 토큰을 소비하므로 API 비용이 빠르게 증가하고, 전용 Cross-encoder 대비 10배에서 100배 느릴 수 있습니다. 후보 문서가 20개이고 각각 500토큰이면, 리랭킹 한 번에 만 토큰 이상을 소비하는 셈입니다. 따라서 비용에 민감한 대규모 프로덕션보다는 정확도가 최우선인 소규모 파이프라인이나 오프라인 배치 처리에 적합합니다. 또한 LLM의 위치 편향(position bias) 문제도 알려져 있어서, 문서의 순서에 따라 평가가 달라질 수 있다는 점도 유의해야 합니다.
클라우드 API 리랭킹 서비스
직접 모델을 호스팅하지 않고 API 호출 한 번으로 리랭킹을 수행할 수 있는 전문 서비스들도 있습니다. Cohere Rerank는 이 분야의 선두주자로, 100개 이상의 언어(한국어 포함)를 지원하며 API 한 번 호출로 최대 1,000개의 문서를 리랭킹할 수 있습니다. rerank-v3.5 모델은 다국어 벤치마크에서 최상위 성능을 기록하고 있습니다.
Jina Reranker도 유사한 서비스를 제공하며, 특히 코드 검색과 기술 문서에서 강점을 보입니다. Voyage AI의 rerank 엔드포인트, Mixedbread의 mxbai-rerank 시리즈도 경쟁력 있는 대안입니다. 이들 서비스의 공통 장점은 인프라 관리 부담이 없고, GPU 서버를 직접 운영하지 않아도 된다는 것입니다. 단점은 데이터가 외부 서버를 거치므로 보안에 민감한 환경에서 사용이 제한될 수 있다는 점, 그리고 호출량에 비례하는 비용이 발생한다는 것입니다. 월 수만 건 이상의 리랭킹이 필요한 고트래픽 시스템에서는 로컬 모델 대비 비용이 높아질 수 있으므로 손익 분기점을 미리 계산해 보는 것이 좋습니다.
한국어 리랭킹, 어디까지 왔나
한국어에 특화된 리랭킹 모델은 영어권에 비해 선택지가 제한적이지만, 상황이 빠르게 개선되고 있습니다. 다국어 모델인 bge-reranker-v2-m3는 학습 데이터에 한국어가 포함되어 있어 한국어 문서 리랭킹에서도 합리적인 성능을 보여 줍니다. Cohere Rerank API 역시 한국어를 공식 지원하며, 특히 rerank-v3.5 모델은 한국어 벤치마크에서 이전 버전 대비 눈에 띄는 성능 향상을 달성했습니다.
국내에서는 업스테이지(Upstage)가 Solar 기반의 리랭킹 모델을 제공하고 있으며, 한국어 문서에 최적화된 성능을 강점으로 내세우고 있습니다. 네이버의 HyperCLOVA 팀도 한국어 검색 품질 향상을 위한 연구를 활발히 진행 중입니다. 한국어 전용 RAG 시스템을 구축한다면, 다국어 모델을 기본으로 사용하되 실제 한국어 질의 응답 데이터로 성능을 비교 평가하는 것을 강력히 권합니다. KorQuAD 같은 공개 벤치마크를 활용하거나, 자체 도메인의 질문-정답 문서 쌍 50에서 100개만 만들어도 모델 간 비교에 큰 도움이 됩니다.

실전 구현: 리랭킹 파이프라인 만들기
원리와 모델 선택 기준을 이해했으니, 이제 실제로 리랭킹을 RAG 파이프라인에 통합하는 방법을 살펴보겠습니다. 가장 기본적인 구현부터 시작해서 프레임워크 활용법, 직접 구현, 그리고 점수 결합 전략까지 단계별로 다루겠습니다.
기본 Cross-encoder 리랭킹 구현
가장 직관적인 방법은 Hugging Face의 sentence-transformers 라이브러리를 사용하는 것입니다. CrossEncoder 클래스를 불러와서 질문-문서 쌍의 리스트를 입력하면, 각 쌍에 대한 관련성 점수를 배열로 반환합니다. 설치는 pip install sentence-transformers 한 줄이면 충분합니다.
구현 흐름은 간결합니다. 먼저 sentence_transformers 패키지에서 CrossEncoder를 임포트하고, 모델명(예: cross-encoder/ms-marco-MiniLM-L-6-v2)을 지정해서 인스턴스를 생성합니다. 그다음 벡터 검색 결과로 가져온 상위 K개 문서 각각을 질문과 쌍으로 묶어 리스트로 만듭니다. 각 쌍은 [질문 문자열, 문서 문자열] 형태의 튜플입니다. 이 리스트를 모델의 predict 메서드에 전달하면, 동일한 길이의 점수 배열이 반환됩니다. 이 점수가 높은 순서대로 문서를 재배열하면 리랭킹이 완료됩니다.
반환되는 점수는 로짓(logit) 값으로, 양수일수록 관련성이 높고 음수일수록 낮다고 해석할 수 있습니다. 시그모이드 함수를 적용하면 0에서 1 사이의 확률값으로 변환할 수 있어 직관적인 해석과 임계값 설정에 편리합니다.
주의할 점이 하나 있습니다. Cross-encoder의 입력 길이에는 토큰 제한이 있습니다. MiniLM 기반 모델은 보통 512 토큰이 상한인데, 질문과 문서를 합친 길이가 이를 초과하면 뒤쪽이 잘립니다. 문서가 긴 경우에는 미리 핵심 부분만 추출하거나, 슬라이딩 윈도우로 여러 구간을 나눠서 각각 점수를 매긴 뒤 최고 점수를 대표값으로 채택하는 전략을 사용해야 합니다. bge-reranker 시리즈처럼 최대 8,192 토큰을 지원하는 모델을 선택하면 이 문제를 상당 부분 완화할 수 있습니다.
프레임워크 활용: LangChain과 LlamaIndex
RAG 프레임워크를 이미 사용하고 있다면, 리랭킹 통합이 더욱 간단해집니다. 두 대표 프레임워크 모두 리랭킹을 파이프라인의 플러그인처럼 추가할 수 있도록 설계되어 있습니다.
LangChain에서는 ContextualCompressionRetriever와 CrossEncoderReranker를 조합합니다. 기존의 벡터 스토어 리트리버를 base_retriever로 설정하고, CrossEncoderReranker를 compressor로 지정한 뒤 ContextualCompressionRetriever로 감싸면 됩니다. 이렇게 구성하면 invoke 메서드를 호출할 때 내부적으로 벡터 검색, Cross-encoder 리랭킹, 상위 N개 반환이 자동으로 순차 실행됩니다. Cohere Rerank API를 쓰고 싶다면 CohereRerank 클래스로 compressor만 교체하면 됩니다. 나머지 파이프라인 코드를 수정할 필요가 없습니다.
LlamaIndex에서는 SentenceTransformerRerank 또는 CohereRerank를 노드 후처리기(node postprocessor)로 사용합니다. QueryEngine을 구성할 때 node_postprocessors 매개변수에 리랭커 인스턴스를 넣어 주면, 검색 결과가 자동으로 리랭킹된 뒤 LLM에 전달됩니다. top_n 매개변수로 리랭킹 후 최종적으로 몇 개의 문서를 LLM에 넘길지 제어할 수 있습니다. 기존에 운영 중인 LlamaIndex 기반 RAG 시스템이 있다면, 한 줄의 설정 추가만으로 리랭킹을 도입할 수 있는 셈입니다.
프레임워크 없이 직접 구현하기
프레임워크에 의존하지 않고 리랭킹을 직접 구현하고 싶은 경우도 있습니다. 파이프라인의 각 단계를 완전히 제어하고 싶거나, 프레임워크의 추상화가 오히려 복잡성을 더한다고 느낄 때 그렇습니다. 이 경우 핵심은 세 가지 함수를 명확하게 분리하는 것입니다.
retrieve 함수는 벡터 데이터베이스에서 상위 K개 후보를 가져오는 역할을 합니다. rerank 함수는 Cross-encoder 모델을 로드하고, 질문과 각 후보 문서의 쌍을 모델에 통과시켜 점수를 매긴 뒤, 점수 순으로 재배열한 상위 N개를 반환합니다. generate 함수는 리랭킹된 문서들을 LLM 프롬프트의 컨텍스트로 넣어 답변을 생성합니다. 이 세 함수를 순차적으로 호출하는 rag_pipeline 함수를 만들면 전체 파이프라인이 완성됩니다. 각 단계가 독립적이므로 리랭커만 교체하거나 retrieve 방식만 바꾸는 등의 유연한 실험이 쉽게 가능합니다.
점수 결합 전략
리랭킹을 도입하면 각 문서에 대해 두 종류의 점수가 생깁니다. 1차 검색의 벡터 유사도 점수와 리랭커의 관련성 점수입니다. 대부분의 경우 리랭커 점수만으로 재배열하는 것이 가장 효과적이지만, 두 점수를 결합하면 성능이 추가로 올라가는 경우도 있습니다.
가장 간단한 결합 방법은 가중 합산(Weighted Sum)입니다. 두 점수를 각각 0에서 1 범위로 정규화(Min-Max Scaling)한 뒤, 리랭커 점수에 0.7, 벡터 점수에 0.3 같은 가중치를 곱해서 더합니다. 가중치는 검증 데이터셋에서 그리드 서치로 최적값을 찾습니다. 리랭커 점수에 더 높은 가중치를 주는 것이 일반적인데, 이는 Cross-encoder의 판단이 Bi-encoder보다 더 정밀하기 때문입니다.
또 다른 방법은 RRF(Reciprocal Rank Fusion)입니다. 하이브리드 검색에서 BM25와 벡터 검색 결과를 결합할 때 널리 쓰이는 이 기법을 리랭킹에도 동일하게 적용할 수 있습니다. 각 점수 체계에서의 순위를 기반으로 최종 점수를 계산하므로, 점수의 스케일이 다른 두 시스템을 결합할 때 별도의 정규화 없이도 잘 작동합니다. 공식은 score = sum(1 / (k + rank_i))로, k는 보통 60으로 설정합니다.
어떤 결합 전략이 최선인지는 데이터와 도메인에 따라 다르므로, 평가 데이터셋을 만들어 직접 비교하는 것이 가장 확실합니다. 경험적으로는 리랭커 모델이 충분히 강력하다면(bge-reranker-v2-m3 이상급) 리랭커 점수 단독 사용이 가장 단순하면서도 효과적인 경우가 많습니다. 결합 전략은 리랭커 모델이 상대적으로 약하거나, 1차 검색의 BM25 점수가 도메인에서 특별히 유용한 경우에 시도해 볼 만합니다.

운영 환경 최적화 전략
리랭킹은 검색 파이프라인에 추가적인 연산 단계를 넣는 것이므로, 응답 시간과 리소스 소비가 증가합니다. 프로덕션 환경에서는 정확도 향상과 성능 비용 사이의 균형점을 찾아야 합니다. 실전에서 검증된 최적화 전략들을 정리합니다.

후보 문서 수(Top-K) 튜닝
리랭킹 성능은 1차 검색에서 몇 개의 후보 문서를 가져오느냐(Top-K)에 크게 좌우됩니다. K가 너무 작으면 정답 문서가 후보에 포함되지 않을 수 있습니다. 리랭킹은 순서를 재배열할 뿐, 없는 문서를 만들어 내지는 못합니다. K가 너무 크면 리랭킹 시간이 선형적으로 늘어나고 관련 없는 노이즈 문서가 혼입될 가능성도 높아집니다.
실무적 가이드라인은 이렇습니다. 먼저 최종적으로 LLM에 넘길 문서 수(Top-N)를 정합니다. 보통 3에서 5개입니다. 그다음 Top-K를 Top-N의 4배에서 10배로 설정합니다. Top-N이 3이면 Top-K는 20에서 30이 적당합니다. 이렇게 하면 리랭킹이 충분한 후보 풀에서 최선을 골라낼 여지가 생기면서도, 연산 부담은 관리 가능한 수준에 머뭅니다.
K 값은 고정해 두지 말고, Recall@K 메트릭을 모니터링하면서 점진적으로 조정하는 것을 권합니다. Recall@K는 상위 K개 후보 안에 정답 문서가 포함된 비율을 뜻합니다. K=20에서 Recall이 0.95 이상이면 충분한 것이고, 0.8 미만이면 K를 늘리거나 1차 검색 자체를 개선해야 합니다. 1차 검색의 Recall이 낮은 상태에서 리랭킹을 아무리 정교하게 해도 효과가 제한적입니다.
지연시간 관리
리랭킹이 추가하는 지연시간은 모델 크기, 후보 수, 문서 길이, 하드웨어에 따라 달라집니다. 일반적인 범위를 모델별로 정리하면 다음과 같습니다.
MiniLM 기반 Cross-encoder(약 80MB)는 CPU 환경에서 20개 문서 리랭킹에 약 50에서 200밀리초가 소요됩니다. 같은 모델을 GPU에서 돌리면 10에서 30밀리초로 줄어듭니다. bge-reranker-v2-m3 같은 중형 모델(약 560MB)은 GPU 기준 20에서 50밀리초, CPU에서는 200에서 500밀리초 정도입니다. LLM 기반 리랭킹은 API 호출 오버헤드까지 합치면 호출당 1초에서 5초가 일반적입니다.
대부분의 RAG 시스템에서 사용자가 허용하는 전체 응답 시간은 스트리밍을 포함해 2초에서 5초입니다. 이 안에 검색, 리랭킹, LLM 생성이 모두 들어가야 합니다. LLM 생성이 보통 첫 토큰까지 1초에서 2초를 차지하므로, 리랭킹에는 500밀리초 이내를 목표로 잡는 것이 현실적입니다.
CPU만 사용할 수 있는 환경이라면 경량 모델(MiniLM-L-6)을 선택하고, 후보 수를 20개 이내로 제한하면 대부분 200밀리초 이내에 리랭킹을 마칠 수 있습니다. ONNX Runtime으로 모델을 변환하면 CPU 추론 속도를 추가로 30에서 50퍼센트 개선할 수 있습니다. sentence-transformers 라이브러리는 ONNX 변환과 추론을 직접 지원하므로 변환 과정도 간단합니다.
배치 처리와 캐싱
동일하거나 유사한 질문이 반복적으로 들어오는 시스템이라면 리랭킹 결과를 캐싱하는 것이 효과적입니다. 질문 텍스트의 해시값과 후보 문서 ID 조합을 캐시 키로 사용해서, 리랭킹 점수와 순위를 Redis 같은 인메모리 저장소에 캐싱합니다. 캐시 히트가 발생하면 Cross-encoder 추론을 완전히 건너뛸 수 있어 응답 시간이 밀리초 단위로 줄어듭니다.
캐시 무효화 전략도 중요합니다. 문서가 업데이트되거나 삭제되면 해당 문서가 포함된 캐시 엔트리를 함께 삭제해야 합니다. TTL(Time-to-Live)을 설정해서 일정 시간이 지나면 자동으로 캐시를 갱신하는 것도 좋은 방법입니다. 문서 업데이트 빈도에 따라 TTL을 1시간에서 24시간 사이로 설정하는 것이 일반적입니다. 변경이 거의 없는 사내 위키라면 24시간, 자주 업데이트되는 고객 지원 문서라면 1시간에서 2시간이 적당합니다.
여러 질문에 대한 리랭킹을 동시에 처리해야 하는 고트래픽 환경에서는 배치 추론이 효과적입니다. GPU를 사용한다면 여러 질문-문서 쌍을 하나의 배치로 묶어 한 번에 추론하면 GPU 활용률이 높아지고 전체 처리량(throughput)이 크게 증가합니다. 배치 크기는 GPU 메모리와 지연시간 요구사항 사이에서 조율해야 합니다. 실시간 응답이 필요한 동기 요청에서는 배치 크기를 작게 유지하고, 비동기 배치 파이프라인에서는 GPU 메모리가 허용하는 한 크게 잡는 것이 일반적입니다.
점수 기반 동적 필터링
리랭킹 점수를 단순히 순위 재배열에만 쓰지 말고, 품질 게이트(quality gate)로 활용하면 답변 품질을 한 단계 더 끌어올릴 수 있습니다. 리랭커 점수가 특정 임계값 이하인 문서는 순위에 관계없이 LLM 컨텍스트에서 제외하는 것입니다. 이렇게 하면 관련 없는 문서가 LLM에 전달되어 환각(hallucination)을 유발하는 것을 방지하고, 정말로 관련 문서가 없을 때 관련 정보를 찾을 수 없습니다라고 솔직하게 답하는 것이 가능해집니다.
구체적으로는 리랭커의 로짓 출력에 시그모이드 함수를 적용해 0에서 1 범위로 변환한 뒤, 예를 들어 0.3 미만인 문서는 무조건 탈락시킵니다. 이 임계값은 도메인과 문서 특성에 따라 달라지므로, 검증 데이터셋에서 Precision과 Recall의 균형을 보며 실험적으로 결정해야 합니다. 임계값을 너무 높게 잡으면 관련 문서까지 탈락시킬 위험이 있고, 너무 낮게 잡으면 필터링 효과가 미미합니다.
더 진보된 방식으로는 상위 N개를 고정하는 대신 점수 기반으로 동적으로 문서 수를 결정하는 전략이 있습니다. 1순위 문서의 점수가 압도적으로 높으면 그 문서 하나만 넘기고, 상위 5개 문서의 점수가 고르게 높으면 5개 모두 넘기는 식입니다. 이 방식은 LLM 컨텍스트를 효율적으로 사용하면서도 필요한 정보는 빠뜨리지 않는 균형을 잡을 수 있습니다. 점수 차이를 기준으로 자연스러운 끊김점(elbow point)을 찾는 알고리즘을 구현하면 자동화도 가능합니다.
모니터링과 지속적 개선
리랭킹을 도입한 뒤에도 지속적인 모니터링이 필요합니다. 핵심 모니터링 지표는 세 가지입니다. 첫째, 리랭킹 지연시간의 p50과 p99를 추적해서 성능 회귀를 조기에 감지합니다. 둘째, 리랭킹 전후의 순위 변동 폭(average rank displacement)을 기록해서 리랭킹이 실제로 의미 있는 재배열을 하고 있는지 확인합니다. 만약 리랭킹 전후로 순위가 거의 변하지 않는다면, 리랭킹 단계가 단순히 지연시간만 추가하고 있을 가능성이 있습니다. 셋째, 사용자 피드백(좋아요, 싫어요, 정답 선택 등)을 수집해서 리랭킹 후 상위에 올라온 문서가 실제로 유용했는지를 정량적으로 측정합니다.
이 지표들을 기반으로 주기적으로 리랭킹 모델을 업데이트하거나, Top-K와 임계값을 재조정하는 선순환 구조를 만드는 것이 장기적으로 RAG 시스템의 품질을 유지하는 핵심입니다. 도메인 특화 데이터가 충분히 축적되면 범용 Cross-encoder를 자체 데이터로 파인튜닝해서 도메인 적합성을 더욱 높이는 것도 고려할 수 있습니다.
리랭킹, RAG의 가성비 최고 업그레이드
지금까지 RAG 시스템에서 리랭킹이 왜 필요하고, 어떻게 작동하며, 실전에서 어떻게 구현하고 최적화하는지를 살펴보았습니다. 핵심을 한 문장으로 요약하면, 리랭킹은 이미 가져온 후보 문서의 순서를 더 정밀한 모델로 다시 매기는 것입니다. 개념은 단순하지만 효과는 극적입니다.
리랭킹이 RAG 개선에서 가장 가성비가 높은 업그레이드인 이유는 분명합니다. 임베딩 모델을 바꾸면 전체 문서를 다시 벡터화해야 하고, 청킹 전략을 바꾸면 인덱스를 처음부터 다시 만들어야 합니다. 하지만 리랭킹은 기존 파이프라인을 전혀 건드리지 않고, 검색 결과와 LLM 사이에 한 단계를 끼워 넣기만 하면 됩니다. 코드 변경은 최소이고, 결과는 즉각적으로 나타납니다.
실전에서 시작하기 위한 권장 순서를 정리합니다. 먼저 cross-encoder/ms-marco-MiniLM-L-6-v2 같은 경량 모델로 프로토타입을 빠르게 만들어 리랭킹의 효과를 직접 확인합니다. 효과가 확인되면 bge-reranker-v2-m3 같은 다국어 모델로 교체해서 한국어 성능을 높입니다. 운영 환경의 지연시간 요구사항에 맞춰 Top-K 값을 조정하고, 필요하면 ONNX 변환이나 GPU 가속을 적용합니다. 마지막으로 점수 기반 필터링을 추가해서 답변 품질의 하한선을 관리합니다.
벡터 검색만으로 대체로 관련 있는 수준에 머물러 있던 RAG 시스템이, 리랭킹 한 단계를 추가하는 것만으로 정확히 필요한 문서를 상위에 보여 주는 시스템으로 변모할 수 있습니다. 이번 글에서 다룬 원리와 실전 기법들을 기반으로 자신의 RAG 파이프라인에 리랭킹을 도입해 보시기 바랍니다. 검색 품질의 차이를 체감하는 데 오래 걸리지 않을 것입니다.
참고 자료
- Retrieval-augmented generation — Wikipedia — RAG의 개념, 구조, 발전 과정을 정리한 위키백과 문서
- Learning to rank — Wikipedia — 리랭킹의 이론적 기반이 되는 학습 기반 랭킹 기법 개요