×
Community Blog 내 RAG는 얼마나 정확할까? 18가지 구성을 직접 벤치마크해 봤습니다

내 RAG는 얼마나 정확할까? 18가지 구성을 직접 벤치마크해 봤습니다

Alibaba Cloud에서 chunk 크기, top-k, reranking을 조합한 18가지 RAG 설정을 벤치마크했습니다. retrieval 없는 기준선 2/30에서 최고 구성 29/30까지 향상됐습니다.

한 고객사에서 RAG demo에 참석한 적이 있습니다. 질문을 던질 때마다 시스템은 문서에서 그럴듯한 답을 찾아왔고, 회의실에 있던 사람들도 고개를 끄덕였습니다.

그러다 한 분이 물었습니다. "그래서 이 시스템은 얼마나 정확한가요?"

순간 회의실이 조용해졌습니다. 답할 수 있는 숫자가 없었기 때문입니다. 몇 가지 질문을 넣어 봤고 답변도 그럴듯했지만, 그것만으로 정확하다고 말할 수는 없었습니다. 프로덕션 도입 여부를 결정하는 자리에서 "대체로 잘 됩니다"는 근거가 되지 못합니다.

저도 그 침묵을 직접 겪고 나서야 깨달았습니다. 그때 답하지 못한 이유는 설명이 부족해서가 아니었습니다. 정확도를 측정할 기준과 방법을 준비하지 않았기 때문입니다.

결국 정확도를 입 밖에 낼 수 있는 숫자로 만들려면, 먼저 세 가지 설정을 정해야 하고, 그 결과를 잴 방법도 있어야 합니다.

  1. chunk는 얼마나 커야 할까요? 256, 512, 1024 단어 중 어떤 크기가 적절할까요?
  2. retrieval은 문서를 몇 개 가져와야 할까요? 3개, 5개, 10개 중 몇 개가 좋을까요?
  3. reranker는 추가 latency와 비용을 감수할 만큼 가치가 있을까요?

그 질문의 답부터 먼저 말하겠습니다. 이 글에서 가장 중요한 숫자이기 때문입니다. 같은 모델이 retrieval 없이 같은 30문항을 풀면 2개만 맞힙니다. retrieval을 붙이면 29개까지 올라갑니다. 모델은 그대로입니다. 이 격차를 만들어낸 것은 모델이 아니라 retrieval이며, 이 글은 바로 그 격차를 잽니다.

이 질문들에 만능 정답은 없습니다. 정답은 corpus, 질문 유형, 요구 품질 수준에 따라 달라집니다. 솔직한 답은 다른 블로그에서 숫자를 골라오는 것이 아니라, 내 시스템에서 직접 측정하는 것입니다.

그래서 이 글은 다른 접근방식을 취합니다. 설정을 추천하는 대신 Alibaba Cloud의 managed service만으로 엔터프라이즈 지식 RAG를 구축했습니다. 모델은 Model Studio, vector search는 AnalyticDB for PostgreSQL을 사용했고 full factorial evaluation을 돌렸습니다. chunk 크기 3가지 × top-k 3가지 × rerank on/off = 18가지 구성 각각에서 retrieval recall, accuracy, groundedness, refusal을 측정했습니다. 아래 숫자는 전부 실측값입니다.

이 글은 아이디어만 설명하는 데 그치지 않고, 신뢰할 수 있는 corpus 구축부터 벡터 index 구성, 재사용 가능한 평가 harness, Batch API로 반값에 judge하는 방법, 실측 결과까지 전 과정을 담았습니다. 모두 실제 환경에서 직접 구축하고 검증한 시스템을 기준으로 합니다. 마지막에는 재사용할 수 있는 harness와 인터랙티브 playground도 함께 제공합니다.

먼저 한 가지 분명히 할 점: 여기서 "accuracy"는 고정된 golden QA set를 LLM-as-judge로 채점한 값이며, 사람이 채점한 값이 아닙니다. 저렴하고 재현 가능하지만 절대적이지 않기 때문에 구성 간 비교 도구로 봐 주세요. 한계 section도 함께 확인하시기 바랍니다.

전체 흐름

시스템은 수집(Ingest), 질의(Query), 평가(Evaluate) 세 갈래입니다. 이 diagram의 핵심은 평가가 질의 옆의 별도 흐름이 아니라는 것입니다. 평가가 Query pipeline 전체를 감쌉니다. matrix가 각 구성마다 pipeline을 한 번씩, 총 18번 구동하고 매 실행의 점수를 매깁니다.

시스템 아키텍처: 한 번 수집한 뒤 평가 루프가 질의 pipeline을 감싸 도는 구조

  1. Ingest 단계에서 corpus를 chunking하고, text-embedding-v4로 embedding해 AnalyticDB for PostgreSQL의 ANN index에 적재합니다. embedding cache가 있어 재실행 시 다시 embedding하지 않습니다.
  2. Query 단계에서 질문을 embedding해 index와 매칭하고, 필요하면 qwen3-rerank로 rerank합니다. rerank on/off 분기는 실험 축 중 하나입니다. 이후 qwen-plus가 엄격한 grounded prompt로 답변합니다.
  3. Evaluate 단계에서 18개 조합 matrix가 조합마다 질의 pipeline을 한 번씩 구동해 648개 답변(36문항 × 18)을 모읍니다. retrieval recall은 결정적으로 계산하고, 답변은 Batch API로 50% 비용에 LLM judge를 씁니다.

핵심은 pipeline과 측정이 하나의 산출물이라는 것 입니다. index를 만드는 harness가 그대로 점수를 매기기 때문에, 설정을 바꿀 때마다 비교 가능한 숫자를 돌려받습니다. 이것이 "RAG 튜닝"을 회귀 테스트로 바꿔 줍니다.

0. 전제 조건

  • Model Studio를 활성화한 Alibaba Cloud 계정
  • Model Studio API 키 (인터내셔널/싱가포르 endpoint)
  • AnalyticDB for PostgreSQL instance (Elastic Storage Mode, Vector Engine Optimization 활성화)
  • region: 이 글 전체에서 싱가포르(ap-southeast-1) 를 사용합니다.

1. 신뢰할 수 있는 corpus, toy data로는 부족합니다

RAG benchmark는 그 지식 기반만큼만 신뢰할 수 있습니다. 손으로 만든 파일 세 개를 넣어 두고 테스트한다면, 실제로는 "유일하게 관련된 문서를 찾을 수 있는가"를 재는 셈입니다. 너무 쉽고, 프로덕션 모습도 아닙니다.

그래서 실제 corpus를 사용했습니다. Model Studio 영문(인터내셔널) 문서 전체(118개 페이지, 약 430만 자)를 스크랩해서 table과 code fence를 살린 채 깔끔한 markdown으로 변환했습니다. 이게 중요한 이유는 두 가지입니다.

  • retrieval 과제가 현실적입니다. 문서가 118개라는 것은 진짜 방해 문서(distraction)가 있다는 뜻입니다. embedding, 가격, rate limit을 다루는 페이지가 여러 개라서, retriever가 아무거나가 아니라 제대로 된 문서를 골라야 합니다.
  • 답변을 권위 있는 출처와 대조할 수 있습니다. 그래야 golden QA set가 의미를 갖습니다.

Model Studio console의 Embeddings 카테고리, text-embedding-v4와 qwen3-rerank

모델은 전부 Model Studio 카탈로그에서 가져옵니다. 이번 구성에서는 embedding에 text-embedding-v4, rerank에 qwen3-rerank, 답변 생성과 judge 모두에 qwen-plus를 사용했습니다.

corpus 선택이 숫자에 미치는 영향: groundedness 점수가 높게 나온 이유 중 하나는 이 corpus가 깔끔하고 권위 있는 문서이기 때문입니다. 스캔본, 혼용 언어, 오래된 페이지처럼 더 지저분한 실제 기업 문서라면 더 어려울 것입니다. 이는 harness를 유지하면서 다시 측정해야 할 이유이지, 이 숫자가 그대로 이전된다고 가정할 이유는 아닙니다.

2. chunking과 embedding

같은 corpus를 256, 512, 1024 단어, 중복 10%의 세 가지 방식으로 chunking했고, 각각 3,087 / 1,397 / 676개 chunk가 나왔습니다. 청커는 markdown을 인식하고, 무엇보다 너무 큰 블록(거대한 markdown table)을 나눠서 단일 chunk가 embedding 모델의 token 한계를 넘지 않게 합니다. 이 부분 때문에 첫 실행에서 InternalError가 발생했습니다. 열이 40개인 가격 table이 하나의 거대한 "문단"이 되었고, 분할 로직을 추가한 뒤에야 해결됐습니다.

각 chunk는 text-embedding-v4를 사용해 1024차원으로 embedding합니다.

구현하며 배운 점: chunk 콘텐츠 해시를 키로 쓰는 embedding 디스크 cache를 추가했습니다. matrix를 재실행할 때마다 수천 개 chunk를 다시 embedding하고 다시 비용을 내야 하는데, cache가 있으면 재실행 시 디스크에서 벡터를 불러옵니다. 변경할 때마다 돌리는 평가라면, 이게 저렴한 regression gate와 비싼 regression gate의 차이입니다.

3. AnalyticDB for PostgreSQL의 벡터 index

vector store는 Elastic Storage Mode의 AnalyticDB for PostgreSQL 7.0이며, instance를 만들 때 Vector Engine Optimization을 활성화한 상태입니다. instance 생성 시 설정하는 옵션이므로 index를 만들기 전에 Enabled로 표시되는지 꼭 확인하세요.

AnalyticDB for PostgreSQL console

구성 예시로, console에서 본 AnalyticDB for PostgreSQL instance입니다. 따라 하시려면 index를 만들기 전에 Vector Engine Optimization이 Enabled로 표시되는지 확인하세요.

위 연결 패널에 Enable RAG Service가 보일 수 있습니다. AnalyticDB for PostgreSQL에는 내장 RAG 기능이 함께 제공됩니다. retrieval pipeline을 빠르게 구축하기 위한 좋은 방법이고, 동작하는 endpoint만 필요하다면 충분히 고려할 만합니다. 여기서 쓰지 않은 이유는 바로 이 글의 목적 때문입니다.
내장 서비스는 chunk 크기, top-k, reranker를 바꾸며 측정할 수 있는 설정값으로 노출하지 않습니다. 이 글은 바로 그 설정들을 측정하는 것이 목표라, pipeline을 직접 만들고 chunking·embedding·retrieval·rerank·generation 모든 단계를 명시적이고 측정 가능한 변수로 만들었습니다. 내장 서비스는 동작하는 RAG로 가는 빠른 길로, 이 글의 harness는 어떤 설정이 실제로 중요한지 알아야 할 때 쓰는 도구로 생각하시면 됩니다.

instance를 만들 때 Vector Search Engine Optimization이 Enabled인지 확인하세요

instance 상세 화면입니다. 하단의 Vector Search Engine Optimization: Enabled가 보입니다. ANN index를 빠르게 만드는 것이 바로 이 옵션입니다.

스키마는 vector(1024) column과 cosine 거리 기반 ANN index를 가진 chunks table 하나입니다.

CREATE TABLE chunks (
    chunk_id  text,
    doc_id    text,
    chunk_seq int,
    content   text,
    embedding vector(1024)
) DISTRIBUTED BY (chunk_id);

CREATE INDEX chunks_vec_idx ON chunks USING ann(embedding)
WITH (dim=1024, distancemeasure=cosine, hnsw_m=16, pq_segments=64);

retrieval은 <=> 연산자로 ANN 근접 탐색을 수행하고, cosine 유사도 기준 top-k를 반환합니다.

SELECT chunk_id, doc_id, content,
       cosine_similarity(embedding, '<query_vector>'::vector) AS score
FROM chunks
ORDER BY embedding <=> '<query_vector>'::vector
LIMIT 10;

직접 테스트해 보니 병목은 retrieval이 아니라 upsert였습니다. 첫 loader는 한 행씩 INSERT했습니다. 약 3,000개 벡터 행을 단일 행 INSERT로 원격 instance에 밀어 넣는 데 수 분이 걸려 멈춘 것처럼 보였습니다. batch 다중 행 INSERT(psycopg2.extras.execute_values)로 바꾸자 3,087행이 약 6초 만에 적재됐습니다. 적재가 멈춘 것 같다면 데이터베이스를 탓하기 전에 INSERT부터 batch로 바꾸세요.

4. 평가 harness, 실제로 남는 자산

pipeline은 바뀔 수 있지만 harness는 계속 남는 자산입니다. 작은 Python 패키지로 chunk, embed, index/retrieve, generate, judge 다섯 가지를 합니다. 핵심은 golden QA setmetric 정의입니다.

4-1. golden set

36개 질문을 직접 작성했고, 서로 다른 실패 모드를 측정하도록 의도적으로 섞었습니다.

유형 개수 측정하는 것
단일 홉(single-hop) 12 사실 하나, 문서 하나
멀티 홉(multi-hop) 10 문서 두 개를 결합해야 함
패러프레이즈(paraphrase) 8 같은 사실을 구어체로 질문해 견고성 확인
unanswerable 6 문서에 답이 없으므로 지어내지 않고 refuse해야 함

답변 가능한 각 질문은 golden source 문서를 명시해서, recall@k를 결정적으로 만듭니다. retrieval된 chunk가 실제로 답을 담은 문서에서 왔는지를 봅니다.

unanswerable 세트는 대부분 건너뛰지만, 가장 가치 있는 부분입니다. "보장된 업타임 SLA 비율이 얼마인가요?" 같은 질문은 corpus에 답이 없습니다. 이런 질문에 자신 있게 답을 지어내는 프로덕션 RAG는 "없다"고 말하는 것보다 나쁩니다. 이 여섯 개 질문이 바로 그걸 측정합니다.

4-2. metric

  • recall@k (결정적): 답변 가능한 질문 중 golden source가 retrieval된 비율입니다. generator와 무관하게 retrieval만 측정합니다.
  • accuracy (LLM judge): 답변이 참조 답변과 일치하는지 봅니다 (correct / partial / incorrect).
  • groundedness (LLM judge): 답변의 모든 사실적 주장이 retrieval된 context에 근거하는지 봅니다.
  • refusal accuracy (LLM judge): unanswerable 질문을 hallucination 없이 refuse하는지 봅니다.

4-3. matrix 실행

harness는 모든 조합을 순회하며 retrieval(필요 시 rerank) → generation → (combo, question)마다 한 행을 씁니다.

# AnalyticDB for PostgreSQL 대상 프로덕션 실행
export DASHSCOPE_API_KEY=sk-xxx
export ADBPG_HOST=... ADBPG_USER=... ADBPG_PASSWORD=... ADBPG_DB=rag_eval
python run_matrix.py --retriever adbpg

# 데이터베이스 없는 개발 루프 (numpy cosine baseline)
python run_matrix.py --retriever local

이렇게 18개 조합 × 36개 질문 = judge할 답변 648개가 생깁니다.

5. 구성 방법

직접 재현하고 싶다면 프로젝트의 실제 구조는 다음과 같습니다. 전체가 작은 Python 패키지 하나이며 별도 프레임워크는 없습니다.

5-1. module 구성

harness/
  config.py            모델, endpoint, matrix 차원, ADBPG 연결, .env loader
  chunker.py           markdown 인식 chunking + 중복 (초과 크기 table 분할)
  embedder.py          text-embedding-v4 + embedding 디스크 cache
  reranker.py          qwen3-rerank (DashScope 네이티브 rerank API)
  retriever_local.py   numpy cosine baseline (데이터베이스 없이 개발)
  retriever_adbpg.py   AnalyticDB ANN index (cosine), batch upsert
  generator.py         엄격한 grounded prompt의 qwen-plus 답변 generation
  judge.py             LLM-as-judge prompt + 출력 parser
  run_matrix.py        전체 matrix 실행, 조합별 JSONL 작성
  batch_judge.py       Batch API로 judge (50% 비용), 결과 병합
  aggregate.py         judge 결과 -> 조합별 recall/accuracy/groundedness
  make_charts.py       이 글의 차트 렌더링
  gen_demo.py          인터랙티브 playground(demo.html) 빌드

데이터 흐름은 일직선입니다. corpus/ → chunk → embedding → ADBPG index → retrieval → (rerank) → generation → judge → metric. 각 단계가 독립 module이라, 예를 들어 generation 모델이나 retriever 같은 한 부분만 교체하고 나머지는 그대로 둘 수 있습니다.

5-2. 단계별 구성

  1. Model Studio를 활성화하고 API 키를 만듭니다. 싱가포르라면 인터내셔널 endpoint(https://dashscope-intl.aliyuncs.com/compatible-mode/v1)를 사용합니다.
  2. AnalyticDB for PostgreSQL instance를 생성합니다. Elastic Storage Mode, High-availability Edition으로 만들고, instance 생성 시 Vector Engine Optimization을 켭니다. 그다음 데이터베이스 계정을 만들고, 클라이언트 IP를 whitelist에 등록하거나 공개 endpoint를 할당해 harness가 접속할 수 있게 합니다.
  3. 의존성을 설치하고 인증 정보를 설정합니다.
# requirements.txt
openai
psycopg2-binary
numpy
matplotlib
scikit-learn
# harness/.env  (chmod 600)
DASHSCOPE_API_KEY=sk-xxxxxxxx
ADBPG_HOST=gp-xxxx.gpdb.singapore.rds.aliyuncs.com
ADBPG_PORT=5432
ADBPG_DB=rag_eval
ADBPG_USER=rag_eval
ADBPG_PASSWORD=********
  1. 입력을 준비합니다. 문서는 corpus/(markdown)에, golden QA set는 golden_set.jsonl에 둡니다(답변 가능한 각 행은 source_docs를 갖습니다).
  2. 세 개의 명령을 실행합니다. run_matrix.py(retrieval + generation), batch_judge.py(half-price judging), aggregate.py(리포트). 필요하면 make_charts.pygen_demo.py로 시각 자료를 만들 수 있습니다.
cd harness
python run_matrix.py --retriever adbpg
python batch_judge.py --dir results --pattern 'run_*.jsonl'
python aggregate.py --file results/results_judged.jsonl

이것이 시스템 전체입니다. embedding, ANN retrieval, judge 같은 무거운 작업은 managed 서비스가 처리하고, harness는 오케스트레이션과 측정만 담당합니다.

6. 개발 방법

위의 설정 숫자들은 결과이지만, 제가 최종 데모를 만드는데까지 과정에 대해 설명드립니다.

로컬에서 먼저 만들고, managed 서비스는 마지막에 올립니다. harness에 retriever가 두 개인 이유가 있습니다. 저는 데이터베이스가 필요 없는 numpy cosine baseline retriever_local.py를 상대로 개발하며, chunking과 golden set, generation prompt를 저렴하고 빠르게 검증했습니다. 루프가 안정된 후에야 AnalyticDB로 올렸습니다.

retrieval과 generation을 따로 측정합니다. recall@k는 결정적이라 retrieval을 격리하고, judge는 generation을 채점합니다. 숫자가 나쁘면 이 분리가 어느 단계를 고쳐야 하는지 알려줍니다. recall이 낮고 accuracy가 높다면 retriever가 문서를 놓치는 것이고, recall이 높고 accuracy가 낮다면 generator가 context를 살리지 못하는 것입니다. 분리 없이는 추측일 뿐입니다.

실제 실패를 반복 개선의 재료로 씁니다. 최종 harness의 "최적화"는 전부 예상해서가 아니라 실제 벽에 부딪혀서 나왔습니다.

  1. 거대한 markdown table이 embedding 한계를 터뜨렸습니다. 40개 column짜리 가격 table이 하나의 초과 크기 "문단"이 되어 embedding API가 InternalError를 던졌습니다. 해결: 청커가 chunk 크기보다 큰 블록을 패킹 전에 먼저 나누도록 했습니다.
  2. 한 행씩 벡터 INSERT하니 멈춘 것처럼 보였습니다. 약 3,000행을 WAN 너머로 한 INSERT씩 밀어 넣는 데 수 분이 걸렸습니다. batch 다중 행 INSERT로 바꾸자 3,087행 적재에 약 6초가 걸렸습니다.
  3. 재실행이 embedding API에 다시 과금했습니다. matrix를 재실행할 때마다 수천 개 chunk를 다시 embedding했습니다. 해결: chunk 콘텐츠 해시를 키로 쓰는 디스크 cache.
  4. corpus 확장이 recall을 조용히 깨뜨렸습니다. corpus를 넓히자 새 문서가 golden topic과 겹쳤고, retriever가 동등한 페이지를 반환하는데 deterministic recall이 이를 미스로 셌습니다. 해결: 각 질문의 source_docs에 동등한 문서를 추가하고, unanswerable 질문이 계속 unanswerable인지 재검증했습니다.
  5. 실시간 judge가 비용 병목이었습니다. 실행마다 648개 답변을 실시간 가격으로 채점하는 것은 지속 불가능합니다. 해결: 50% 비용의 Batch API.

평가를 regression gate로 씁니다. golden set은 harness와 함께 버전 관리에 포함합니다. 새 문서, 새 모델, 새 chunk 크기 등 어떤 변경이든 재실행해서 이전 metric과 diff를 냅니다. "내가 개선했나?"라는 질문을 느낌이 아니라 숫자로 바꿔 줍니다.

7. Batch API로 반값에 judge하기

실시간 LLM으로 648개 답변을 채점하는 것은 루프에서 가장 비싼 부분입니다. 그리고 corpus나 설정을 바꿀 때마다 다시 돌려야 하는 부분이기도 합니다. 그래서 648개 judge 호출을 Model Studio Batch API 작업 하나로 묶었습니다. 24시간 윈도우 내에서 실시간 가격의 50% 로 실행됩니다.

같은 qwen-plus judge, temperature=0, 같은 prompt를 사용하면서 비용은 절반이고 코드 복잡도는 늘어나지 않습니다. OpenAI 호환 /v1/batches endpoint를 그대로 사용합니다. chat-completion 요청을 JSONL로 만들어 upload하고, polling한 뒤 결과를 병합하면 됩니다.

# 648개 답변을 50% 비용으로 judge, results_judged.jsonl로 병합
python batch_judge.py --dir results --pattern 'run_*.jsonl'

# 조합별 metric으로 집계
python aggregate.py --file results/results_judged.jsonl

반복할 평가라면, Batch API가 "변경할 때마다 다시 측정"을 감당할 수 있게 해 줍니다.

8. 결과

숫자를 보기 전에 두 가지 주의사항부터 짚겠습니다.

두 표는 표본 수가 다르므로 다르게 읽어야 합니다. 8-3의 조합별 수치는 각각 답변 가능한 30개 문항으로 계산하므로 한 문항이 3.3점입니다. 개별 조합 간 10점 미만의 차이는 한두 문항 차이에 불과하고 judge의 실행 간 분산 안에 있습니다. 순위가 아니라 noise로 봐야 합니다. 반면 8-1의 marginal mean은 성격이 다릅니다. 각각 6개 구성, 즉 답변 가능한 문항-instance 180개를 평균하므로 훨씬 안정적입니다. 아래 결론을 개별 셀이 아니라 marginal mean에서 끌어오는 이유가 바로 이것입니다.

통제 실험: retrieval 없이 모델이 이미 답을 아는가? 이 숫자들이 의미가 있는지를 가르는 질문입니다. corpus는 Model Studio 자체 문서이고, generator와 judge는 모두 qwen-plus입니다. pretraining에서 이 문서들을 봤을 가능성이 있는 모델입니다. 이미 답을 안다면 아래 accuracy는 retrieval이 아니라 암기일 수 있습니다. 그래서 같은 36개 문항을 qwen-plusretrieval context 없이, 중립 prompt와 같은 judge 기준으로 돌렸습니다.

Closed-book (retrieval 없음) RAG 최고 구성
답변 가능 정답률 0.067 (2/30) 0.967
refusal accuracy 0.000 1.000

retrieval 없이는 qwen-plus가 30개 중 2개만 judge를 통과합니다. 그런데 둘 다 속이 빈 "정답"입니다. 하나는 숫자는 맞췄지만 다른 회사 문서를 출처로 잘못 제시했습니다. 다른 하나는 아예 답변을 회피했고, 거기에 내려진 "correct"는 지식이 아니라 후한 판정에 가깝습니다. 실질적으로 closed-book recall은 0에 가깝습니다. 파라메트릭 지식만으로는 Alibaba Cloud Model Studio를 다른 회사의 동명 제품과 혼동하기까지 합니다. 즉 아래 accuracy는 모델이 문서를 외워서가 아니라 retrieval이 만든 결과입니다. closed-book에서는 unanswerable 질문을 refuse하지도 못합니다. refusal accuracy 1.0은 grounded prompt와 retrieval context가 만들어낸 것입니다.

Closed-book과 retrieval 증강의 accuracy 비교, 같은 모델에 같은 질문

같은 모델과 같은 답변 가능 30문항을 사용했습니다. retrieval 없이는 2개만 judge를 통과하고, retrieval을 더하면 30개 중 29개입니다.

최고 단일 구성은 다음과 같습니다: chunk 1024 · top-k 10 · rerank on

metric 점수
recall@10 0.967
accuracy 0.967
groundedness 1.000
refusal accuracy 1.000

18개 구성 전체에서 recall은 0.767~0.967, accuracy는 0.833~0.967이었습니다. groundedness는 모든 구성에서 0.94 이상이었고, refusal accuracy는 전 구성에서 1.0이었습니다. grounded prompt는 unanswerable 질문에 대해 모델이 답을 지어내지 못하게 했습니다.

8-1. 어떤 설정이 품질에 가장 큰 영향을 주나?

chunk 크기가 가장 큰 변수입니다.

chunk 크기별 marginal mean, 각 chunk 크기의 6개 구성

256 → 512 → 1024 단어로 가면서 recall은 0.867 → 0.911 → 0.950, accuracy는 0.861 → 0.850 → 0.906으로 올랐습니다. 큰 chunk는 주변 context를 온전히 유지해서 retriever가 단편이 아니라 완결된 문단을 generator에 넘겨줍니다. 512에서의 작은 accuracy 하락은 judge 실행 간 분산 이내입니다. 256→1024 추세는 명확합니다.

이 recall 비교에는 공정성 주의가 하나 필요합니다. 고정 k에서는 큰 chunk일수록 query당 corpus의 더 큰 비율을 가져옵니다. k=10이면 676개 chunk-1024 index의 약 1.5%지만, 3,087개 chunk-256 index의 약 0.3%에 불과합니다. 즉 chunk 크기 recall 이득의 일부는 chunking 효과가 아니라 token 예산 부산물입니다. 엄밀한 비교는 retrieval token 예산을 일정하게 맞춰야 합니다(작은 chunk는 같은 token량을 가져올 때까지 k를 올립니다). 방향은 유지됩니다. 여기서도 큰 chunk가 유리합니다. 하지만 이 고정 k 비교가 chunk 크기 우위의 크기를 다소 과장한다는 점은 감안해서 보세요.

Top-k는 명확하지만 더 작은 효과입니다.

Top-k별 marginal mean, 각 k의 6개 구성

recall은 0.872 (k=3) → 0.922 (k=5) → 0.933 (k=10) 으로 오릅니다. 후보를 더 많이 retrieval하면 reranker와 generator가 더 많은 재료를 얻습니다. 이득은 이미 k=10에서 평탄해지고 있습니다. 10 너머는 측정하지 않았지만, 평탄해진 지점 이후의 추가 후보는 recall보다 latency와 비용을 더할 가능성이 큽니다.

rerank는 recall을 사고, accuracy는 대략 현상 유지입니다.

reranker marginal mean, rerank off vs on

reranker를 켜면 recall이 0.893 → 0.926으로 올랐습니다. accuracy는 사실상 동일(0.874 → 0.870)했고, groundedness는 약간 하락(0.978 → 0.969)했습니다. 즉 reranker의 역할은 제대로 된 문서를 context 윈도우 안으로 끌어올리는 것입니다. 켤지 말지는 recall과 latency/비용 사이의 trade-off입니다.

8-2. 전체 그리드에서의 recall

chunk 크기와 top-k에 따른 recall@k heatmap

두 가지가 눈에 띕니다. 첫째, 큰 chunk와 높은 k 방향으로 갈수록 recall은 감소하지 않습니다(non-decreasing). 오르거나 유지될 뿐 떨어지지 않습니다. rerank-on 1024 행은 0.97에서 평탄하고, 512 행은 k=5부터 평탄해집니다. 둘째, 리랭킹이 곡선을 평탄하게 만듭니다. rerank를 켜면 작은 chunk와 낮은 k의 조합도 ~0.87에 도달하고, 1024 행 전체가 0.97에서 포화됩니다. 하나의 설정만 조정할 수 있다면 chunk 크기를 선택하세요. reranker는 평범한 구성을 건져 올리는 역할입니다.

8-3. 18개 조합 전체

조합 recall accuracy ground refusal p95 latency query당 비용
c256_k3_r0 0.767 0.867 0.972 1.0 4.4s $0.0005
c256_k3_r1 0.867 0.833 0.944 1.0 4.5s $0.0010
c256_k5_r0 0.867 0.867 1.000 1.0 4.8s $0.0008
c256_k5_r1 0.900 0.867 0.944 1.0 6.4s $0.0013
c256_k10_r0 0.900 0.867 0.972 1.0 5.9s $0.0017
c256_k10_r1 0.900 0.867 0.972 1.0 5.9s $0.0020
c512_k3_r0 0.833 0.867 0.944 1.0 6.6s $0.0010
c512_k3_r1 0.900 0.833 0.972 1.0 5.2s $0.0020
c512_k5_r0 0.933 0.867 0.972 1.0 6.0s $0.0016
c512_k5_r1 0.933 0.867 0.972 1.0 6.0s $0.0026
c512_k10_r0 0.933 0.833 0.972 1.0 6.1s $0.0033
c512_k10_r1 0.933 0.833 0.972 1.0 6.7s $0.0041
c1024_k3_r0 0.900 0.933 1.000 1.0 3.4s $0.0017
c1024_k3_r1 0.967 0.867 0.944 1.0 5.3s $0.0037
c1024_k5_r0 0.933 0.833 0.972 1.0 4.4s $0.0031
c1024_k5_r1 0.967 0.900 1.000 1.0 5.6s $0.0052
c1024_k10_r0 0.967 0.933 1.000 1.0 6.0s $0.0071
c1024_k10_r1 0.967 0.967 1.000 1.0 6.9s $0.0088

latency는 query당 end-to-end로 측정했습니다. 질의 embedding부터 ANN retrieval, 필요 시 rerank, generation까지 제 클라이언트에서 측정했으므로 싱가포르까지의 네트워크 왕복이 포함됩니다. 비용은 각 호출의 실제 token 사용량에 인터내셔널 정가를 적용해 계산했습니다.

latency column에 대한 주의. 조합당 실행이 1회뿐이라 n=36에서 여기의 p95는 사실상 두 번째로 느린 단일 관측값입니다. 클라이언트와 싱가포르 사이의 왕복 시간, 그리고 generation 시간의 분산이 인접 조합 간 차이보다 큽니다. column을 세로로 읽으면 스스로 모순되기도 합니다. rerank on이 off보다 빠른 조합이 셋이고, 1024단어 chunk가 256단어 chunk보다 빠릅니다. 이 column은 방향성 참고로만 보세요. rerank는 generation 전에 네트워크 호출을 하나 더 추가합니다. 정확한 초 단위 수치는 각자 환경에서 반복 측정해야 합니다. 비용은 token 정산이므로 이 noise와 무관하게 결정적입니다.

이제 "reranker를 켤까?"라는 질문에 숫자로 답할 수 있습니다. reranker를 켜면 generation 전에 네트워크 호출이 하나 더 붙고, query당 비용이 약 47%($0.0023 → $0.0034) 늘어나는 대신 recall을 +3.3포인트 얻습니다. latency는 방향성만 말할 수 있습니다. 조합당 1회 실행이라 조합별 초 단위 수치는 noise입니다. 비용 쪽이 확실한 이유는 token 정산이기 때문입니다. 비용은 generator의 입력 token이 지배하므로 chunk 크기와 k가 커질수록 함께 증가합니다. 큰 chunk를 더 많이 가져오면 context에 들어가는 텍스트가 많아집니다. 최고 조합은 query당 약 $0.009입니다. 트래픽이 많은 서비스라면 더 저렴한 조합인 c1024_k3_r0($0.002, recall 0.90)이 더 나은 선택일 수 있습니다.

최악 구성(chunk 256, k=3, rerank 없음)은 recall 0.767, 최고 구성은 0.967입니다. 설정만으로 20포인트 차이가 납니다. 같은 모델, 같은 corpus에서 말입니다. 설정은 사소한 것이 아닙니다. 배포할 수 있는 RAG와 그럴 수 없는 RAG의 차이입니다.

9. 직접 확인해 보기: 인터랙티브 playground

표 안의 숫자도 설득력 있지만 retrieval이 일어나는 모습을 직접 보는 편이 더 이해하기 쉽습니다. 평가 전체를 독립 실행형 페이지 demo.html 하나로 묶었습니다. 어떤 브라우저에서든 열 수 있고 서버나 API 키가 필요하지 않습니다. 안의 모든 내용은 이번 실행에서 나온 실제 trace이거나 실측값입니다. 세 가지 view를 제공합니다.

Retrieval Map. corpus의 676개 chunk 전체를 t-SNE로 2차원에 펼치고 소스 문서별로 색칠합니다. 36개 golden question 중 하나를 고르면 vector search가 어떤 chunk를 retrieval했는지 주황색과 순위로 표시하고, golden source 문서는 초록색 링으로 보여줍니다. retrieval이 무엇을 하고 어디서 놓치는지 보는 가장 직관적인 방법입니다.

인터랙티브 retrieval 맵

다른 질문을 고르면 highlight가 해당 클러스터로 이동합니다

Tune the Config. chunk 크기, top-k, reranker on/off를 움직이면, 그 조합의 실측 recall / accuracy / groundedness를 heatmap에서 바로 읽을 수 있습니다.

인터랙티브 설정 tuner

같은 tuner를 최악의 구성인 chunk 256, k=3, rerank off로 돌리면 recall이 0.767까지 떨어집니다

Pipeline Walkthrough. 어떤 질문이든 retrieval, rerank, generation, judge의 전 과정을 단계별로 볼 수 있습니다. retrieval된 chunk, highlight된 golden source, judge 판정을 함께 보여줍니다. 아래는 정답 사례와 refusal 사례입니다.

pipeline walkthrough, 근거 있는 정답

pipeline walkthrough, unanswerable 질문 refusal

정답 사례에서는 retrieval이 golden 문서를 찾아 qwen-plus1024라고 답했고, correct + grounded 판정을 받았습니다. refusal 사례에서는 unanswerable 질문에 답을 지어내는 대신 "Not found in the knowledge base"라고 응답했습니다.

10. 알아두면 좋은 동작 세부 사항

  • recall@k는 결정적이지만 judge metric은 그렇지 않습니다. recall은 golden source 문서로 계산하므로 실행마다 재현 가능합니다. judge metric은 temperature 0의 LLM이라 실행 간에 작은 분산이 있습니다. 한두 문항 차이는 noise로 보고(8절), harness를 비교 도구로 쓰세요.
  • refusal을 만들어내는 것은 모델이 아니라 prompt입니다. generator 시스템 prompt는 context에 답이 없을 때 "Not found in the knowledge base"라고 그대로 답하도록 강제합니다. 이 한 줄과 retrieval context가 함께 refusal accuracy를 1.0으로 만듭니다. closed-book(8절)에서는 같은 모델이 이 질문들을 하나도 refuse하지 못합니다.
  • judge는 generator가 본 것과 같은 context를 읽습니다. groundedness는 전체 corpus가 아니라 retrieval된 context를 기준으로 판정합니다. 모델이 실제로 본 내용이 답을 지지하는지 측정하기 위한 의도적인 설계입니다.

11. 한계

  • judge는 LLM이지 사람이 아닙니다. temperature 0에서는 자기 자신과 잘 일치하고 구성 비교에는 충분하지만, 사람이 채점한 ground truth는 아닙니다. 절대 수치를 신뢰하기 전에 샘플을 직접 확인하세요.
  • judge와 generator가 같은 모델(qwen-plus)이라 자기 선호(self-preference) 편향 우려가 있습니다. 이를 점검하려고 최고 조합의 답변을 독립 judge 모델(qwen-max)로 다시 채점했고, correctness 30/30, refusal 6/6이 일치했습니다. 각각 답변 가능 30문항과 unanswerable 6문항입니다. 이 결과가 사람 기준으로도 정답임을 증명하지는 않지만, 모델이 자신에게 후한 점수를 준 결과만은 아님을 보여줍니다.
  • chunk 크기 지정에는 근사 token 수를 사용했습니다. 청커가 공백으로 구분된 단어를 기준으로 나누기 때문에 "512단어" chunk가 정확히 512 model token인 것은 아닙니다. 8-3의 비용 수치는 각 API 호출이 보고한 실제 token 사용량으로 계산했습니다.
  • groundedness가 높은 이유 중 하나는 corpus가 깔끔하고 권위 있기 때문입니다. 더 지저분한 실제 문서는 더 어려울 것입니다. 가정하지 말고 다시 측정하세요.
  • 하나의 generation 모델(qwen-plus)만 측정했습니다. config에서 모델을 바꾸고 다시 실행하면 됩니다. 그런 비교를 가능하게 하는 것이 바로 이 harness의 목적입니다.

마무리

RAG 설정을 추측할 필요는 없습니다. 프로덕션급 pipeline을 위해 자체 GPU 팜이나 벡터 데이터베이스를 운영할 필요도 없습니다. 모델은 Model Studio, vector search는 AnalyticDB for PostgreSQL을 사용하고, golden set, deterministic recall, Batch API 기반 LLM judge를 포함한 체계적인 평가 루프를 구성하면 됩니다. 그러면 정확도를 단순히 기대하는 것이 아니라 숫자로 말할 수 있는 시스템이 됩니다.

  • closed-book에서는 같은 모델이 30개 중 2개만 맞힙니다. accuracy 이득은 모델이 문서를 외워서가 아니라 retrieval이 만든 것입니다.
  • chunk 크기가 품질에 가장 큰 영향을 미쳤습니다. 문서형 콘텐츠에서는 1024를 기준선으로 시작해볼 수 있지만, 이를 보편적인 정답으로 볼 수는 없습니다. 동일한 k에서도 청크가 클수록 더 많은 전체 컨텍스트를 검색하므로, 검색되는 총 토큰 수를 동일하게 맞춘 조건에서도 이 경향이 유지되는지 다시 검증해야 합니다.
  • k=3보다 k=5·k=10이 낫습니다. 5→10 이득은 작고(recall 0.92 → 0.93), 10 시점에 이미 곡선이 평탄해지고 있습니다.
  • rerank를 사용하면 네트워크 호출 하나와 query당 비용 약 47%를 추가하는 대신 recall을 약 3포인트 얻습니다. groundedness는 거의 변하지 않습니다. 이제 수치로 판단할 수 있는 trade-off입니다.
  • 엄격한 grounded prompt가 전 구성에서 refusal accuracy 1.0을 만들었습니다. 가장 저렴한 hallucination 방어입니다.
  • Batch API로 judge하면 평가 비용이 절반이고, 독립 judge 모델이 결과에 correctness 30/30, refusal 6/6 일치했습니다. "변경할 때마다 다시 측정"이 저렴할 뿐 아니라 방어 가능해집니다.

harness, golden set, 인터랙티브 playground는 그대로 가져다 쓸 수 있습니다. "어떤 모델을 고를까"에 쏟는 정성만큼 "정확한지 어떻게 알까"도 지금 바로 설계하고 측정하세요!


참고 문서

English Version :Click

Alibaba Cloud
Hosung Kim | Sr.Technical Account Manager

0 0 0
Share on

Hosung Kim

6 posts | 1 followers

You may also like

Hosung Kim

6 posts | 1 followers

Related Products