당신의 RAG 평가가 불안정한 것이 아닙니다. 당신의 검색(Retrieval)이 비결정론적(Non-Deterministic)인 것입니다.
요약
RAG 평가 결과가 불안정한 원인이 모델이 아닌 검색(Retrieval) 단계의 비결정론적 동작에 있음을 설명합니다. SQL 정렬 시 동점자 처리 규칙이 명시되지 않으면 실행마다 순서가 바뀌어 RRF 결과와 평가 지표가 달라질 수 있습니다.
핵심 포인트
- RAG 평가 지표의 변동은 모델이 아닌 검색 순서의 비결정론성 때문일 수 있음
- RRF(Reciprocal Rank Fusion)는 점수가 아닌 순위에 의존하므로 순서가 매우 중요함
- SQL 정렬 시 점수가 같을 경우를 대비해 ID 등을 활용한 tie-breaker가 필수적임
- 결정론적 정렬을 통해 검색 결과의 재현성을 확보해야 신뢰할 수 있는 평가가 가능함
동일한 쿼리.
동일한 문서.
동일한 모델.
그럼에도 RAG 평가는 여전히 다른 Recall@8 값을 반환할 수 있습니다.
모델이 불안정해서가 아닙니다. ORDER BY 절 때문입니다.
저는 지표가 흔들리는 것을 보고 이를 발견한 것이 아닙니다. 검색(Retrieval) 코드를 읽다가, 모델이 전혀 바뀌지 않더라도 실행할 때마다 점수가 드리프트(drift)될 수 있다는 사실을 깨달았습니다.
이는 제가 최근에 채택한 습관에서 비롯되었습니다. 저는 기능을 만들기 전에 평가(Eval)를 먼저 작성합니다. "이 책에 물어보세요(Ask this Book)" 기능 뒤에 있는 검색 파이프라인을 검토하던 중, 그것을 발견했습니다. 검색 계층이 비결정론적(Non-deterministic)이었던 것입니다.
순서는 표현 방식이 아닙니다. 그것은 입력의 일부입니다.
저의 RAG 구현은 의도적으로 단순합니다. 일반적인 PostgreSQL과 .NET을 사용합니다. 동일한 테이블에 대해 두 가지 검색 전략을 사용합니다:
- pgvector를 사용한 의미론적 검색 (Semantic search)
- PostgreSQL 전문 검색 (Full-text search)을 사용한 어휘 검색 (Lexical search)
결과들은 상호 순위 융합 (Reciprocal Rank Fusion, RRF) 방식으로 병합됩니다.
여기 중요한 부분이 있습니다: RRF는 검색 점수(Retrieval scores)에는 관심이 없습니다. 오직 순위(Rank)에만 관심이 있습니다.
만약 한 검색기가
A
B
C
을 반환하는 대신
B
A
C
을 반환한다면,
RRF는 서로 다른 융합 점수(Fused scores)를 생성합니다. 서로 다른 융합 점수는 서로 다른 Top-K를 의미합니다. 서로 다른 Top-K는 서로 다른 Recall@K를 의미합니다.
RRF에서 순서는 단순한 표시 세부 사항이 아닙니다. 순서는 데이터입니다.
버그
저의 어휘 검색(Lexical query)은 다음과 같이 끝났습니다:
ORDER BY score DESC
완벽하게 합리적으로 보입니다. 하지만 ts_rank_cd는 놀라울 정도로 자주 동점(Ties)을 발생시킵니다. 여러 청크(Chunks)가 정확히 동일한 점수를 가질 수 있습니다.
그리고 SQL은 당신이 명시적으로 요청한 정렬만을 보장합니다. 만약 여러 행이 동일한 것으로 비교된다면, PostgreSQL은 어떤 순서로든 반환할 수 있는 자유가 있습니다.
아무것도 바뀌지 않았습니다. 동일한 데이터베이스. 동일한 쿼리. 동일한 모델. 오직 동일한 순위의 행들의 순서만 바뀌었을 뿐입니다. 하지만 그것만으로도 RRF는 서로 다른 순위를 할당하여, 서로 다른 융합 점수와 다른 평가 결과를 만들어내기에 충분합니다.
의미론적 검색(Semantic retrieval)도 동일한 문제를 가지고 있었습니다. 거리(Distance) 동점은 어휘적 동점보다 훨씬 드물지만, "드물다"는 것은 평가 파이프라인에 있어 충분하지 않습니다.
해결책
해결책은 당황스러울 정도로 작았습니다.
이전:
ORDER BY score DESC
이후:
ORDER BY score DESC, id
두 검색 쿼리 모두에 결정론적 (Deterministic) 동점 처리 규칙(tie-breaker)을 적용했습니다. 이제 점수가 동일한 행들은 항상 동일한 순서로 나타나며, RRF는 매 실행마다 동일한 입력을 받고, Top-K 결과도 동일하게 유지됩니다.
무엇이 일어나지 않았는지 주목하십시오. 검색 성능 자체가 좋아진 것은 아닙니다. 재현 가능 (Reproducible)해진 것입니다.
이것이 중요한 이유
우리는 평가 과정에서 모델을 결정론적 (Deterministic)으로 만들기 위해 많은 노력을 기울입니다: Temperature 0 설정, 고정된 데이터셋, 정답 (Golden answers), 재현 가능한 프롬프트 (Prompts) 등 말입니다.
하지만 모델 하부의 모든 과정이 이미 결정론적일 것이라고 가정하기 쉽습니다. 하지만 실제로는 그렇지 않은 경우가 많습니다. 검색 (Retrieval), 순위 매기기 (Ranking), 샘플링 (Sampling), 데이터 로딩 (Data loading) 등 파이프라인 내의 어떤 비결정론적 (Non-deterministic) 단계라도 조용히 당신의 평가를 무효화할 수 있습니다.
변동성이 큰 평가는 단순히 짜증 나는 문제가 아닙니다. 그것은 위험합니다. 결국 당신은 평가 수치가 실제 문제를 가리키고 있을 때조차 그 숫자를 신뢰하지 않게 됩니다.
내가 얻은 교훈
모델을 디버깅하기 전에, 결정론 (Determinism)을 디버깅하십시오. 평가는 그것에 데이터를 공급하는 파이프라인만큼만 결정론적일 수 있습니다. 동일한 쿼리. 동일한 행. 동일한 순서. 오직 그럴 때만이 당신의 평가가 말해주는 내용을 신뢰할 수 있습니다.
저는 .NET으로 기술 서적을 위한 오픈 소스 리더인 TextStack을 만들고 있습니다. 이 내용은 해당 서비스의 "이 책에 대해 질문하기 (Ask this Book)" 기능 뒤에 있는 검색 계층 (Retrieval layer)에서 가져온 것입니다. github.com/mrviduus/textstack
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기