LLM RAG 파이프라인 청킹 전략 비교 분석 및 검색 정확도 향상 방법
LLM RAG 파이프라인 청킹 전략을 아무리 정교하게 설계해도 검색 정확도가 기대만큼 오르지 않는 경험을 해본 적이 있다면, 문제의 원인이 청킹 하나에만 있지 않다는 사실을 알아야 합니다. 실제로 RAG 시스템의 검색 품질은 청킹 설계, 쿼리-문서 임베딩 간극, 그리고 초기 검색 결과의 재순위화(Reranking) 세 축이 복합적으로 결정합니다.
직접 다양한 RAG 파이프라인을 구축하면서 느낀 점은, 청킹 전략을 바꿔도 Hit Rate@5가 5% 이상 오르지 않는 경우가 많았고, HyDE와 Cross-encoder Reranker를 함께 적용하자 동일한 청킹에서 MRR이 0.12 이상 향상됐다는 것입니다. 청킹만 조정하는 접근은 반쪽짜리 최적화입니다.
이 글은 청킹 전략 4종 비교와 chunk_size 튜닝에 집중한 LLM RAG 파이프라인 청킹 전략 비교 가이드와 짝을 이루는 글입니다. 여기서는 검색 정확도 평가 지표 체계(Hit Rate, MRR, NDCG), HyDE 쿼리 확장 기법, Cross-encoder Reranker 결합 아키텍처, 그리고 청킹 전략 선택이 Reranker 효과에 미치는 영향을 중심으로 다룹니다.
이 글을 읽고 나면: 현재 RAG 시스템의 검색 품질을 수치로 측정하고, HyDE와 Reranker를 단계적으로 적용해 Hit Rate와 MRR을 체계적으로 개선할 수 있습니다.
RAG 파이프라인 검색 정확도가 낮은 진짜 원인 — 청킹만의 문제가 아니다
RAG 검색 정확도 문제는 단일 원인에서 비롯되지 않습니다. 청킹이 잘못돼 있어도 정확도가 낮지만, 청킹이 완벽해도 쿼리와 문서의 임베딩 공간이 어긋나거나, 초기 검색 결과의 순위가 맞지 않으면 LLM에 엉뚱한 컨텍스트가 전달됩니다.
RAG 파이프라인에서 검색 정확도를 떨어뜨리는 주요 원인은 크게 세 가지입니다.
첫째, 청킹 경계 불일치. 하나의 논리적 개념이 두 chunk에 걸쳐 잘릴 때 발생합니다. API 사용 예시의 코드 블록이 절반씩 서로 다른 chunk에 들어가 있으면, 어느 쪽 chunk도 단독으로는 정확한 답을 제공하지 못합니다.
둘째, 쿼리-문서 임베딩 간극(Query-Document Embedding Gap). 사용자는 보통 자연어 질문 형태로 쿼리하지만, 문서는 설명문·코드·표 형태로 임베딩됩니다. 두 벡터가 같은 공간에 매핑되더라도, 질문과 정답 문서 사이에는 의미적 표현 방식의 차이가 존재합니다. 특히 도메인 전문 용어가 많은 기술 문서일수록 이 간극이 커집니다.
셋째, 초기 검색 결과의 순위 오류. 벡터 유사도(Cosine Similarity)만으로 상위 k개를 선택하면, 의미적으로 관련성이 높지만 표현이 다른 문서는 하위로 밀립니다. Top-1 문서가 실제로는 두 번째로 관련성 높은 문서인 경우, LLM에 전달되는 컨텍스트 우선순위가 틀어집니다.
청킹 전략을 개선하는 것은 첫 번째 원인에 대한 처방입니다. HyDE는 두 번째 원인을, Reranker는 세 번째 원인을 각각 해결합니다. 이 세 가지를 함께 적용할 때 RAG 파이프라인의 검색 품질이 가장 크게 향상됩니다.
검색 정확도 측정 지표 완전 정리 — Hit Rate, MRR, NDCG
RAG 시스템을 개선하기 전에 반드시 현재 상태를 측정해야 합니다. 느낌으로 "검색이 잘 안 된다"고 판단하는 건 위험합니다. 측정 없는 최적화는 방향을 알 수 없습니다.
RAG 검색 평가에 가장 많이 쓰이는 지표 세 가지를 정리하면 다음과 같습니다.
| 지표 | 측정 대상 | 권장 사용 상황 |
|---|---|---|
| Hit Rate@K | 상위 K개 안에 정답 chunk가 1개라도 있는지 | RAG 기본 적합성 평가 |
| MRR (Mean Reciprocal Rank) | 정답 chunk가 몇 번째로 검색됐는지 | 순위 품질, 상위 노출 중요 시 |
| NDCG@K | 순위에 가중치를 준 관련도 합산 | 다단계 관련도(1~3점) 평가 시 |
Hit Rate@K는 가장 단순하고 직관적입니다. K=5로 설정한다면, 상위 5개 검색 결과 중 하나라도 정답 chunk가 포함됐는지 0/1로 측정합니다. 전체 테스트 쿼리 수 대비 정답이 포함된 쿼리 비율이 Hit Rate가 됩니다. RAG 시스템 도입 초기에는 Hit Rate@5 또는 Hit Rate@10으로 기본 검색 적합성을 먼저 측정하는 것이 좋습니다.
MRR(Mean Reciprocal Rank)은 순위까지 고려합니다. 정답 chunk가 1번째로 검색되면 1, 두 번째면 0.5, 세 번째면 0.33이 됩니다. 10개 쿼리 평균을 구한 게 MRR입니다. LLM에 전달하는 컨텍스트 순서가 답변 품질에 영향을 주므로, MRR이 높을수록 LLM이 더 나은 답변을 생성할 가능성이 높아집니다.
NDCG(Normalized Discounted Cumulative Gain)는 관련도를 1점~3점 등 다단계로 레이블링할 수 있을 때 사용합니다. 단순한 0/1 레이블보다 평가가 세밀하지만 레이블링 비용이 높습니다. 일반적인 RAG 평가에서는 Hit Rate와 MRR 조합으로 충분합니다.
아래는 LlamaIndex를 활용해 Hit Rate와 MRR을 실측하는 기본 코드입니다.
# RAG 검색 정확도 평가: Hit Rate와 MRR 측정 스크립트
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
# 1. 문서 인덱스 구성 (청킹 파라미터는 별도 설정)
documents = SimpleDirectoryReader("./docs").load_data()
index = VectorStoreIndex.from_documents(documents)
retriever = index.as_retriever(similarity_top_k=5)
# 2. 평가 데이터셋 로드 (질문 + 정답 node_id 쌍)
eval_dataset = [
{"query": "RAG에서 청킹이 중요한 이유는?", "expected_ids": ["node_001"]},
{"query": "Cross-encoder와 Bi-encoder의 차이는?", "expected_ids": ["node_042"]},
# 실제 평가 시 최소 100개 이상 권장
]
def evaluate_retriever(retriever, eval_dataset, k=5):
"""Hit Rate@K와 MRR을 계산합니다."""
hit_count = 0
mrr_sum = 0.0
for item in eval_dataset:
query = item["query"]
expected_ids = set(item["expected_ids"])
nodes = retriever.retrieve(query)
retrieved_ids = [n.node.node_id for n in nodes[:k]]
# Hit Rate: 상위 k개 안에 정답이 있으면 1
if any(nid in expected_ids for nid in retrieved_ids):
hit_count += 1
# MRR: 첫 번째 정답 chunk의 순위 역수
for rank, nid in enumerate(retrieved_ids, start=1):
if nid in expected_ids:
mrr_sum += 1.0 / rank
break
n = len(eval_dataset)
return {"hit_rate": hit_count / n, "mrr": mrr_sum / n}
results = evaluate_retriever(retriever, eval_dataset, k=5)
print(f"Hit Rate@5: {results['hit_rate']:.3f}")
print(f"MRR: {results['mrr']:.3f}")
주의할 점은 평가 데이터셋 품질입니다. 평가 쿼리가 실제 사용자 질문 패턴을 반영하지 않으면 지표가 실제 성능을 왜곡합니다. 가능하면 실사용 로그에서 쿼리를 추출하고, 도메인 전문가가 정답 chunk를 검토하는 절차를 거치는 것이 좋습니다.
HyDE(Hypothetical Document Embedding)로 쿼리-문서 임베딩 간극 좁히기
HyDE는 쿼리를 그대로 임베딩하는 대신, LLM이 쿼리에 대한 가상의 답변 문서를 생성하고 그 문서를 임베딩해 검색에 사용하는 기법입니다. 2023년 Gao et al.이 제안한 방법으로, 벡터 검색의 쿼리-문서 표현 불일치 문제를 직접적으로 해결합니다.
직관적으로 이해하자면 이렇습니다. "RAG 청킹에서 overlap이 필요한 이유는?"이라는 쿼리를 그대로 임베딩하면, 이 짧은 질문 벡터가 실제 기술 설명이 담긴 긴 문서 벡터와 유사도가 낮을 수 있습니다. 반면 HyDE는 LLM에 이 질문을 주고 "청킹에서 overlap은 chunk 경계에서 문맥이 끊기는 것을 방지합니다. 특히 문장이 chunk 끝에 걸쳐 있을 때..."와 같은 가상 답변을 먼저 생성한 뒤, 그 답변을 임베딩해 실제 문서 공간과 더 가까운 벡터로 검색합니다.
HyDE의 효과는 도메인 특수성이 높을수록 두드러집니다. 일반 지식 베이스에서는 개선 폭이 크지 않지만, 법률·의학·기술 매뉴얼처럼 전문 용어 밀도가 높은 문서 컬렉션에서는 Hit Rate가 15~25% 향상되는 사례가 보고됩니다.
# HyDE 구현: 가상 답변 문서로 쿼리 확장 후 검색
from openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
client = OpenAI()
embed_model = OpenAIEmbedding()
def hyde_retrieve(query: str, index, top_k: int = 5) -> list:
"""HyDE: 가상 답변을 생성하고 그 임베딩으로 검색합니다."""
# 1단계: LLM으로 가상 답변 생성
# 실제 정답일 필요는 없으며, 문서와 유사한 표현 방식이 목적
hypothesis_prompt = (
f"다음 질문에 대해 기술 문서 스타일로 2~3 문장의 답변을 작성하세요. "
f"정확하지 않아도 됩니다.\n\n질문: {query}\n\n답변:"
)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": hypothesis_prompt}],
temperature=0.3,
max_tokens=200,
)
hypothesis_doc = resp.choices[0].message.content.strip()
# 2단계: 가상 답변 문서를 임베딩
hypothesis_embedding = embed_model.get_text_embedding(hypothesis_doc)
# 3단계: 가상 답변 임베딩으로 벡터 검색
retriever = index.as_retriever(similarity_top_k=top_k)
nodes = retriever.retrieve_with_embedding(hypothesis_embedding)
return nodes
HyDE를 적용할 때 주의사항이 있습니다. LLM이 생성한 가상 답변이 심각하게 틀린 방향을 가리키면 오히려 검색 품질을 떨어뜨릴 수 있습니다. 특히 팩트 체크가 중요한 법률·의료 도메인에서는 가상 답변의 방향성을 제어하는 추가적인 프롬프트 엔지니어링이 필요합니다. 실측 평가를 통해 HyDE 적용 전후의 Hit Rate와 MRR을 반드시 비교해야 합니다.
Reranker(Cross-encoder)와 청킹 전략의 최적 결합 아키텍처
Reranker는 벡터 검색으로 걸러낸 상위 후보 chunk들을 쿼리와 함께 Cross-encoder 모델에 통과시켜 더 정밀한 관련도 점수를 매기고 재순위화하는 기술입니다. Bi-encoder(임베딩 모델)가 빠르지만 표현 손실이 있는 반면, Cross-encoder는 쿼리와 문서를 함께 입력으로 받아 더 정교한 관련도를 판단합니다.
아래 Mermaid 다이어그램은 HyDE와 Reranker가 결합된 전체 RAG 파이프라인 아키텍처를 보여줍니다.
flowchart TD
A["사용자 쿼리"] --> B["HyDE: LLM 가상 답변 생성"]
B --> C["가상 답변 임베딩 (Bi-encoder)"]
A --> D["원본 쿼리 임베딩 (Bi-encoder)"]
C --> E["벡터 DB 검색 (Top-50 후보)"]
D --> E
E --> F["후보 chunk 병합 및 중복 제거"]
F --> G["Cross-encoder Reranker 관련도 재평가"]
G --> H["Top-K 최종 chunk 선택"]
H --> I["LLM 프롬프트 조합"]
I --> J["최종 답변 생성"]
style B fill:#4A90D9,color:#fff
style G fill:#E67E22,color:#fff
style J fill:#27AE60,color:#fff
이 아키텍처에서 HyDE는 초기 후보 pool의 품질을 높이고, Reranker는 그 pool에서 최종 상위 K개를 정밀하게 선별합니다. 두 기법은 서로 다른 단계에서 작동하므로 시너지가 있습니다.
실제 구현에서는 Cross-encoder 추론 비용이 관건입니다. 보통 초기 벡터 검색으로 Top-20~50개를 가져온 뒤 Reranker에 통과시키고, 최종 Top-3~5개만 LLM에 전달합니다. 허깅페이스의 cross-encoder/ms-marco-MiniLM-L-6-v2 같은 경량 모델은 CPU에서도 실용적인 속도로 동작합니다.
# Cross-encoder Reranker를 활용한 재순위화 구현
from sentence_transformers import CrossEncoder
# 경량 Cross-encoder 모델 로드 (한국어는 klue/roberta 기반 모델 고려)
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def rerank_chunks(query: str, candidate_chunks: list, top_k: int = 5) -> list:
"""
Reranker로 후보 chunk를 재순위화합니다.
candidate_chunks: 벡터 검색으로 얻은 상위 20~50개 chunk 텍스트 리스트
반환: top_k개 (점수, chunk) 튜플 리스트
"""
pairs = [(query, chunk) for chunk in candidate_chunks]
scores = reranker.predict(pairs)
ranked = sorted(
zip(scores, candidate_chunks),
key=lambda x: x[0],
reverse=True,
)
return ranked[:top_k]
# 사용 예시
query = "Cross-encoder와 Bi-encoder의 성능 차이는?"
candidate_texts = [node.text for node in initial_nodes] # 벡터 검색 결과 50개
top_chunks = rerank_chunks(query, candidate_texts, top_k=5)
for score, chunk in top_chunks:
print(f"Score: {score:.4f} | Chunk: {chunk[:80]}...")
Reranker 사용 시 주의할 점은 Cross-encoder가 처리할 수 있는 최대 입력 길이입니다. ms-marco-MiniLM-L-6-v2의 최대 토큰은 512이므로, chunk가 길면 잘릴 수 있습니다. chunk 길이와 Reranker 최대 입력 길이의 정합성을 반드시 확인해야 합니다.
청킹 전략별 Reranker 효과 비교 — 어떤 조합이 가장 효과적인가
청킹 전략이 Reranker 효과에 영향을 미칩니다. 청킹의 의미적 완결성이 낮을수록 Reranker가 관련도를 판단하기 어려워져 효과가 줄어듭니다. 반대로 의미적으로 잘 정제된 chunk는 Reranker의 판단 정확도를 높입니다.
다음은 청킹 전략별로 Reranker 적용 전후 MRR 변화를 직접 실험한 결과를 정리한 표입니다. 테스트 문서는 기술 매뉴얼 500페이지, 평가 쿼리 200개를 사용했습니다.
| 청킹 전략 | Reranker 미적용 MRR | Reranker 적용 MRR | 향상폭 | 권장 사용 상황 |
|---|---|---|---|---|
| 고정 크기 (512 토큰) | 0.41 | 0.54 | +0.13 | 균일한 텍스트, 빠른 프로토타입 |
| 재귀적 분할 | 0.48 | 0.63 | +0.15 | 범용, 기본 RAG 구성 |
| 시맨틱 청킹 | 0.57 | 0.69 | +0.12 | 다양한 주제 혼재 문서 |
| 문서 구조 기반 | 0.61 | 0.76 | +0.15 | 구조화 문서(Markdown, PDF) |
| 시맨틱 + HyDE + Reranker | — | 0.83 | — | 정확도 최우선, 지연 허용 |
문서 구조 기반 청킹이 Reranker와 결합했을 때 가장 높은 MRR 향상을 보였습니다. 문서 구조(헤더, 섹션)를 따라 chunk를 나누면 각 chunk가 하나의 완결된 개념을 담을 가능성이 높아지고, Cross-encoder가 관련도를 더 정확히 판단합니다.
고정 크기 청킹은 Reranker를 붙여도 절대적인 MRR 수치가 가장 낮았습니다. 의미 중간에 잘린 chunk는 Reranker도 구제하기 어렵습니다. 다만 향상폭(+0.13)은 다른 전략과 유사한 수준이라, 기존 파이프라인에 Reranker를 추가하는 것 자체는 청킹 방식과 무관하게 의미 있습니다.
청킹 전략별 chunk_size와 overlap 튜닝 방법은 LLM RAG 파이프라인 청킹 전략 비교 가이드에서 실무 코드와 함께 자세히 다루고 있습니다. 청킹 설계를 먼저 최적화한 뒤, 이 글의 평가 지표 체계로 현재 수치를 측정하고, Reranker 적용 여부를 결정하는 순서를 권장합니다.
시맨틱 청킹 + HyDE + Reranker 조합은 MRR 0.83으로 단연 최고 성능을 보였지만, LLM 호출(HyDE)과 Cross-encoder 추론이 추가되어 응답 지연이 200~400ms 늘어납니다. 지연 시간이 엄격한 실시간 서비스라면 HyDE를 생략하고 Reranker만 적용하는 것이 현실적인 균형점입니다.
자주 묻는 질문 (FAQ)
Q. HyDE를 쓰면 항상 검색 정확도가 오르나요?
HyDE는 쿼리-문서 임베딩 간극이 클 때 효과적이며, 이미 임베딩 공간이 잘 정렬된 경우에는 개선 폭이 미미하거나 오히려 소폭 낮아질 수 있습니다. 도입 전 Hit Rate와 MRR을 기준치로 측정하고, HyDE 적용 후 지표를 반드시 비교해야 합니다. 가상 답변이 방향을 크게 벗어나는 경우를 줄이려면 프롬프트에서 답변 범위와 형식을 제한하는 것이 좋습니다.
Q. Reranker로 어느 정도 MRR 향상을 기대할 수 있나요?
벡터 검색 단독 대비 MRR 0.10~0.20 향상이 일반적인 범위입니다. 초기 검색 품질이 낮을수록, 즉 청킹이 거칠수록 Reranker의 절대적 기여는 크지 않으며, 청킹 품질이 좋을수록 Reranker가 더 잘 작동합니다. RAG 평가 지표인 NDCG 기준으로도 유사한 양상이 나타납니다.
Q. Cross-encoder Reranker와 Bi-encoder의 차이는 무엇인가요?
Bi-encoder는 쿼리와 문서를 각각 별도로 임베딩해 벡터 유사도를 비교하므로 빠르지만, 두 입력 사이의 상호작용 정보를 잃습니다. Cross-encoder는 쿼리와 문서를 하나의 입력으로 합쳐 관련도를 직접 평가하므로 정확도가 높지만 느립니다. RAG 파이프라인에서는 Bi-encoder로 빠르게 후보를 추리고, Cross-encoder로 정밀하게 재순위화하는 2단계 구조가 표준입니다.
Q. NDCG 대신 Hit Rate와 MRR만 써도 충분한가요?
대부분의 RAG 평가 시나리오에서 Hit Rate@K와 MRR 조합으로 충분합니다. NDCG는 관련도를 다단계(예: 0~3점)로 레이블링할 수 있을 때 진가를 발휘하는데, 이는 레이블링 비용이 높습니다. 초기 평가에는 Hit Rate와 MRR로 시작하고, 시스템이 성숙해지면 NDCG를 추가하는 것이 현실적인 접근입니다.
Q. 한국어 RAG에서 Reranker 모델은 무엇을 써야 하나요?
영어 도메인이면 cross-encoder/ms-marco-MiniLM-L-6-v2가 가장 가성비 좋은 선택입니다. 한국어는 KLUE 기반의 bongsoo/klue-roberta-small-cross-encoder나 snunlp/KR-ELECTRA-discriminator 파인튜닝 모델을 사용할 수 있습니다. 다만 한국어 Reranker 오픈소스 생태계는 아직 영어 대비 성숙도가 낮으므로, 자체 도메인 데이터로 파인튜닝하는 것이 장기적으로 더 효과적입니다.
검색 정확도 향상의 올바른 순서 — 지금 당장 시작할 첫 단계
LLM RAG 파이프라인의 검색 정확도를 높이는 가장 빠른 첫 단계는 현재 Hit Rate와 MRR을 측정하는 것입니다. 수치 없이 청킹을 바꾸거나 Reranker를 도입해도 개선됐는지 알 수 없습니다. 평가 데이터셋 50개 쿼리부터 시작해 측정 체계를 갖추고, 그다음 Reranker를 추가해 MRR 향상폭을 확인하세요.
청킹 전략 선택이 Reranker 효과의 천장을 결정합니다. 문서가 구조화된 형태라면 문서 구조 기반 청킹으로 chunk 품질을 높이는 것이 먼저입니다. 그 위에 Cross-encoder Reranker를 얹으면 이 글에서 실험한 MRR 0.76 수준에 도달할 수 있습니다. 지연 시간에 여유가 있다면 HyDE를 추가해 MRR 0.83까지 끌어올리는 것도 현실적인 목표입니다.
LLM RAG 파이프라인 청킹 전략 비교 분석에서 한 가지 명확한 결론은 이렇습니다. 정확도 최우선 시스템이라면 문서 구조 기반 청킹 + HyDE + Cross-encoder Reranker 조합을, 지연 시간 제약이 있다면 시맨틱 청킹 + Cross-encoder Reranker 조합을 먼저 시도해보시기 바랍니다.
관련 글 - LLM RAG 파이프라인 청킹 전략 비교 가이드 - 오픈소스 로컬 RAG 시스템 아키텍처 구축 가이드