크로스인코더 리랭커
앞단 검색이 좁혀준 후보 20개쯤을 다시 정렬하는 모델.
바이인코더 vs 크로스인코더
| 방식 | 속도 | 정확도 | |
|---|---|---|---|
| 바이인코더 (Dense 검색) | 질문과 청크를 따로 벡터화 후 거리 비교 | 빠름 (미리 계산 가능) | 보통 |
| 크로스인코더 | 질문과 청크를 붙여서 함께 읽고 점수 | 느림 (매번 계산) | 높음 |
크로스인코더는 미리 계산해둘 수 없다. 질문이 와야 계산이 시작된다. 그래서 전체 30,000청크에 못 쓰고, 앞단이 20개로 좁혀준 뒤에만 쓴다.
질문 → [Dense/BM25로 20개 추림] → [크로스인코더가 20개 재정렬] → 상위 5개
빠름 느리지만 정확
언제 효과가 큰가
정답이 후보 안에는 있는데 등수가 낮을 때. 후보에 아예 없으면 리랭커도 못 만든다 — 앞단이 못 찾은 건 끝이다.
우리 상황이 정확히 이렇다: Dense가 정답을 상위 20에 갖고 있으면서 15등, 20등에 놓는 경우가 많다. 리랭커가 유망한 이유.
실측 — 채택 (2026-07-29, 164문항, e5-small 베이스)
가설이 맞았다. 승격 기준 +0.05의 두 배를 넘겼다.
세 지표가 같이 올랐다. MRR만 오르고 Recall이 떨어졌다면 “1등은 잘 맞히는데 나머지를 놓친다”는 뜻이라 위험했을 것이다.
유형별로도 손해 본 곳이 없다 — 단일세부 +0.137, 직답 +0.189, 탐색비교 +0.034, 후속 +0.005. 뒤 둘은 여러 문서를 걸치는 유형이라 개선폭이 작다.
상세 → 검색 실험 결과
이 폭은 e5-small 위에서의 값
임베딩이 좋아지면 리랭커가 고칠 게 줄어 +0.104보다 작아질 것으로 본다. 방향(플러스)은 유지되겠지만 크기는 재봐야 한다. 리랭커 모델끼리는 아직 비교 안 했다 — 쓴 건
bge-reranker-v2-m3하나뿐. → 검색 실험 결과 §3
비용
| 모델 | BAAI/bge-reranker-v2-m3 — 568M 파라미터 |
| 실행 | GPU 필요 (우리는 VM L4 23GB). GPU가 없으면 dense 단독으로 자동 강등(RERANKER_REQUIRE_GPU=True) |
| 속도 | VM L4에서 문항당 0.27초 (164문항 45초) / 맥 CPU 13.27초 — 49배 |
| 위험 | 웹 서버에서 동시 요청 시 모델 중복 로드 → OOM. 이중검사 락 + MODEL_LOAD_TIMEOUT_S=300 유한 대기로 해소 |
왜 GPU가 필요한가 — 49배가 나오는 이유
바이인코더는 청크 벡터를 미리 계산해 저장해둔다. 질문이 오면 질문 하나만 벡터로 바꿔 거리를 비교하면 끝이라, 질문 시점에 모델이 읽는 텍스트는 질문 1개뿐이다.
크로스인코더는 그게 안 된다. 점수가 (질문, 청크) 쌍에 대해 정의되므로 미리 계산할 대상이 없고, 질문마다 질문 + 청크를 붙인 입력 20개를 통째로 순전파해야 한다.
바이인코더 질문 1개 순전파 → 나머지는 저장된 벡터와 거리 계산(산수)
크로스인코더 (질문+청크) 20개 순전파 → 매 질문마다 처음부터
568M 모델의 순전파 20회는 GPU에서는 병렬로 흘러 0.27초에 끝나지만, CPU에서는 그 연산이 그대로 노출돼 13.27초가 된다. 이 49배 차이는 모델이 느려서가 아니라 질문 시점에 해야 하는 연산량 자체가 크기 때문이고, 그래서 후보를 20개로 좁힌 뒤에만 쓰는 것이다.
맥은 MPS가 아니라 CPU로 돈다 — _load_cross_encoder가 MPS 경로 불안정을 이유로 cuda/cpu만 쓴다.
실측 조건: 후보 20개, 워밍업 분리, 3회 중앙값 → 검색 실험 결과 §5