나의 RAG 모델은 이미 책을 읽고 있었다 - 도서별 판정, 가짜 회귀(Regression), 그리고 9B 모델의 사전 지식 유출 포착
요약
RAG 시스템 운영 중 발생한 성능 저하 원인을 분석하며, 모델이 제공된 컨텍스트 대신 사전 학습된 지식(Parametric knowledge)을 사용하는 현상을 포착했습니다. 해리 포터 코퍼스를 활용해 모델의 충실성을 테스트하고 검색 예산 및 지식 유출 문제를 다룹니다.
핵심 포인트
- 동일한 입력 조건에서도 RAG 성능이 저하되는 가짜 회귀 현상 분석
- 모델이 컨텍스트 대신 사전 학습된 지식을 사용하는 '지름길' 문제 지적
- 코퍼스 규모 확대에 따른 검색 예산(Retrieval budget)의 한계 확인
- NVIDIA Nemotron Nano 9B 모델을 활용한 실무적 포렌식 접근법
나의 평가 결과에 따르면 나의 RAG(Retrieval-Augmented Generation) 성능이 저하되었습니다. 5월에서 7월 사이에 PASS(통과) 비율이 20개 중 9개에서 20개 중 5개로 떨어졌습니다. 동일한 데이터베이스, 동일한 프롬프트(Prompts), 동일한 vLLM 버전, 동일한 모델을 사용했습니다. 나는 결국 저장된 요청 페이로드(Request payloads)를 바이트 단위로 비교(diff)했습니다. 결과는 63,430자, 완전히 동일했습니다. 모든 입력 비트가 같았습니다. 이 "회귀 (Regression)"는 노이즈였습니다. 이를 증명하기 위해 두 달간의 포렌식 재구성 작업이 필요했는데, 만약 5줄짜리 메타데이터 스탬프(Metadata stamp)만 있었어도 불필요한 작업이 되지 않았을 것입니다.
이것은 이 프로젝트에서 나온 세 가지 이야기 중 하나입니다. 나머지 두 가지는 다음과 같습니다: 코퍼스(Corpus)가 14배 커졌을 때 조용히 작동을 멈춰버린 검색 예산(Retrieval budget), 그리고 검색된 발췌문'만' 사용해야 하는 답변에 프리트레이닝(Pretraining) 지식을 계속 몰래 집어넣는 9B 모델에 관한 이야기입니다. 이 현상은 GPU나 별도의 판정 모델(Judge model) 없이도 기계적인 방식으로 포착할 수 있습니다.
이 글은 나의 기술 보고서를 읽기 쉽게 정리한 버전입니다. 모든 작업은 vLLM을 통해 서비스되는 NVIDIA Nemotron Nano 9B v2를 탑재한 일반 소비자용 컴퓨터(RTX 5090, 32 GB VRAM) 한 대에서 실행되었습니다.
설정: 모델이 이미 알고 있는 코퍼스
코퍼스는 해리 포터(Harry Potter) 소설 7권 전체입니다: 136만 단어로 구성되어 있으며, 250단어씩 겹치게(Overlapping) 5,579개의 조각으로 나누어 40MB 크기의 단일 SQLite 파일(FTS5 인덱스 및 BGE-large 임베딩(Embeddings)이 포함된 sqlite-vec 테이블)에 저장되었습니다. 쿼리 임베딩(Query embedding)은 CPU에서 실행되므로 GPU는 생성(Generation) 작업에만 전념할 수 있습니다.
왜 이 코퍼스(Corpus)인가요? 그것은 바로 _충실성(Faithfulness)에 대한 적대적(Adversarial) 테스트_가 되기 때문입니다. Berkeley의 "Speak, Memory" 연구는 GPT급 모델들이 학습 데이터의 다른 내용만큼이나 해리 포터(Harry Potter) 시리즈를 철저하게 암기하고 있음을 보여주었으며, Microsoft의 "Who's Harry Potter?" 언러닝(Unlearning) 논문은 바로 그 지식이 제거하기 매우 어렵다는 점 때문에 이 시리즈를 선택했습니다. 따라서 제가 모델에게 "제공된 발췌문만을 사용하여 답변하세요"라고 말할 때, 모델은 _모든 질문_에 대해 사용할 수 있는 파라미터적 지름길(Parametric shortcut)을 가지고 있습니다. 만약 모델이 그 지름길에 의존하게 된다면, 이 코퍼스는 그 실수를 감지할 수 있게 해줍니다. 즉, 모델이 컨텍스트(Context)에서 제외된 엔티티(Entity)를 생성할 수 있게 되는 것입니다.
통합된 답변이 아닌, 도서별 판정
기초 측정 단계: 저는 1권(400개 청크)만을 대상으로 "덤블도어(Dumbledore)가 해리(Harry)에게 크리스마스 선물로 무엇을 주었나요?"라고 물었고, 검색(Retrieval)은 정확히 찾아냈습니다. 동일한 쿼리를 동일한 Top-15 예산(Budget)으로 7권 전체(5,579개 청크)에 대해 실행했을 때의 결과는 다음과 같습니다:
| 범위 | 방법 | cloak chunk | note chunk | Ron's ID chunk | 재현율 (Recall) |
|---|---|---|---|---|---|
| 1권 (400개 청크) | trigram | rank 323 | 13 | 15 | 3/3 |
| ... |
경쟁 풀(Pool)의 크기를 제외하고는 아무것도 변하지 않았습니다. 승자들은 나중에 나온 권수의 크리스마스/덤블도어/해리 관련 구절들이었습니다. 키워드 중복도는 높았지만, 관련성(Relevance)은 전혀 없었습니다. 400개 청크에 맞춰 조정된 검색 예산은 5,579개 청크에서는 조용히 작동을 멈춥니다.
저의 해결책은 유행을 따르지 않는 방식입니다. 도서당 한 번씩 전체 파이프라인을 실행하는 것입니다. 각 도서는 엄격한 프롬프트(Prompt) 하에 자체적인 검색과 자체적인 모델 호출을 수행합니다. 프롬프트 내용은 "장(Chapter) 인용과 함께 1~4문장으로 답변하거나, 정확히 NOT_FOUND_IN_THIS_BOOK라고 출력하세요"입니다. 이렇게 하면 7개의 독립적인 판정이 생성되며, 이를 나란히 놓고 비교할 수 있습니다.
의도적으로 병합 단계(merge step)를 두지 않았습니다. 병합된 답변은 조작된 판정을 근거가 있는 판정과 함께 세탁해 버립니다. 제가 "볼드모트는 어떻게 영혼을 나누었나요?"라고 물었을 때, 6권은 그 메커니즘을 기여하고 7권은 우발적인 호크룩스(Horcrux) 결말을 기여하며, 1~5권은 올바르게 답변을 유보합니다. 7개의 카드가 이를 보여줍니다. 하나의 합성된 단락은 어떤 책이 무엇을 말했는지 숨기게 되며 — 조각들을 조립할 만큼 똑똑한 어떠한 애그리게이터(aggregator)라도 제가 배제하려고 노력 중인 바로 그 사전 지식 지름길(prior-knowledge shortcut)을 다시 도입하게 될 것입니다. (SPD-RAG나 PAGE-RAG와 같은 최근 논문들도 문서별로 분해하지만, 마지막에 이를 집계합니다. 병합하지 않는 디스플레이 방식은 제가 연구용으로서 가장 강력하게 옹호할 부분입니다.)
비용: 모델 호출 횟수의 7배. 로컬 환경에서는 돈이 아니라 지연 시간(latency)의 문제입니다. 20개 질문 × 7권 전체 평가를 동시성(concurrency) 3으로 실행하면 11~15분이 소요됩니다.
존재하지 않았던 회귀(regression)
5월에 저는 네 개의 검색 백엔드(retrieval backends)를 대상으로 평가를 실행했습니다. 최선의 결과는 PASS 9/20이었습니다. 7월에 저는 정확히 저장된 설정을 다시 실행했습니다: PASS 5/20.
결론을 내리기 전에, 저는 저장된 5월의 요청 페이로드(request payloads)를 새로 생성된 것과 비교했습니다. **바이트 단위로 동일(Byte-identical)**했습니다 — 동일한 63,430자의 컨텍스트, 동일한 시스템 프롬프트(system prompt), 동일한 샘플링 파라미터(sampling parameters), 동일한 모델 리비전(model revision), 동일한 vLLM을 사용했습니다. 따라서 남은 유일한 변수는 온도(temperature) 0.3에서의 샘플링뿐이었습니다. 140개의 도서별 판정(20개 질문 × 7권)에 대한 분해 결과는 다음과 같습니다:
- 123/140 (88%)이 두 달의 간격을 두고 정확히 재현됨
- 17번의 뒤집힘: +9개의 새로운 거짓 양성(false-positive) 도서, -4개의 사라진 거짓 양성, +3개의 새로 발견된 예상 도서, -1개의 유실된 도서
- 실제 참 양성(True-positive) 커버리지는 오히려 개선됨 (예상 도서 19개 → 21개 발견)
PASS 점수의 하락은 전적으로 "하나의 추가적인 가짜 도서가 PASS를 PARTIAL로 강등시켰기 때문"이었습니다. 정확한 집합 점수 방식(Exact-set scoring)은 단 하나의 도서 결과가 뒤집혀도 판정이 바뀝니다. 시드(seed)를 설정하지 않은 온도 0.3에서의 단일 실행은 20개 질문 세트에서 ±4의 변동을 보이는 노이즈가 많은 도구입니다 — 이 중 두 개를 비교하는 것은 존재하지 않는 회귀(regression)를 "감지"하는 꼴이 됩니다.
결정적인 사실은 다음과 같습니다: 나의 5월 로그에도 실제 성능 붕괴(4일 만에 PASS 9 → 0으로 급락)가 포함되어 있었는데, 나는 이를 모델의 이상 현상(model weirdness)으로 치부해 버렸습니다. 페이로드 포렌식(Payload forensics) 결과, 이는 기록되지 않은 설정 변경 때문이었음이 드러났습니다. 누군가(나 자신)가 컨텍스트(context)를 약 2.4배 확장하고 max_tokens를 두 배로 늘렸던 것입니다. 컨텍스트가 길어질수록 정밀도(precision)는 떨어졌습니다. 넓은 컨텍스트를 사용한 실행에서는 한 번의 실행에서 최대 **91개의 도서 판정 오류(false-positive book verdicts)**가 발생했습니다. 존재하지 않는 회귀를 보는 것과 설정 드리프트(config drift)를 모델의 탓으로 돌리는 것, 이 두 가지 실패 모드는 모두 동일한 근본 원인에서 비롯되었습니다. 바로 아무것도 기록(stamped)하지 않았다는 점입니다.
해결 방법
- 모든 요청에 시드(Seed)를 설정하세요. vLLM은 요청당
seed를 허용합니다. 측정 결과: 동시성(concurrency) 3에서 시드를 설정한 두 번의 실행 결과, 판정 결과는 15/15회 동일했으며, 답변 내용은 14/15회 바이트 단위로 일치했습니다. 남은 미세한 변동은 배치 구성의 비결정성(batch-composition nondeterminism) 때문입니다. 서빙 부하에 따라 커널 형태(kernel shapes)가 변하므로, 비트 단위의 정확성(bit-exactness)을 얻으려면 동시성을 1로 설정해야 합니다. - 모든 실행을 기록(Stamp)하세요. 윈도우(Window), top-K,
max_tokens,seed, 프롬프트 버전 및 해시(hash), 모델 및 해결된 리비전(resolved revision), 리포지토리 커밋(repo commit) 정보를 결과 JSON에 포함하세요. - 모델 리비전을 고정하세요. 나는 Hugging Face 캐시가 콜드 스타트(cold start) 시마다
refs/main을 다시 해결(re-resolving)하여, 두 달 동안
볼드모트는 호크룩스 (Horcruxes) 생성을 통해 자신의 영혼을 나누었다 — 영혼의 파편을 담고 있는 물건들... [4권 32장 §5]
"Horcrux"라는 단어는 995개의 4권 청크(chunks) 전체에서 단 한 번도(zero times) 나타나지 않습니다. 이 단어는 6권(58회)과 7권(118회)에만 존재합니다. 모델은 사전 학습(pretraining) 데이터로부터 답변을 생성했으며, 이를 조작된 인용(citation)으로 감쌌습니다.
이 사례는 마치 부정행위처럼 느껴질 정도로 저렴한 탐지기(detector)의 가능성을 시사했습니다. 즉, 발견된(FOUND) 모든 답변에 대해, 모델에게 주어진 컨텍스트(context) 어디에도 나타나지 않는 용어들을 플래그(flag)로 표시하는 방식입니다. 판정 모델(judge model)도, GPU도 필요 없으며, 순수하게 어휘적(lexical) 방식(단순 어간 추출 포함)으로 작동합니다. 또한, 테스트 프레임워크(harness)가 모든 페이로드(payload)를 저장하기 때문에, 제가 지금까지 수행한 모든 실행에 소급 적용할 수 있습니다. 저는 15번의 실행에 걸쳐 저장된 671개의 FOUND 답변을 감사(audit)했습니다.
두 가지 유형의 신호(signal)가 있습니다:
- 엔티티 유출 (Entity leaks) — 문장 중간에 근거 없이 대문자로 표기된 용어들입니다. FOUND 답변의 43%가 적어도 하나 이상의 유출을 포함하고 있습니다. 주요 사례는 다음과 같습니다:
| 유출된 엔티티 (Leaked entity) | 횟수 (Count) | 결정적 이유 (Why it's damning) |
|---|---|---|
| horcrux(es) | 41 | 6 |
| ... |
- 밀집 유출 (Dense leaks) — 8개 이상의 근거 없는 용어가 포함된 답변입니다. 이것들은 조작된 에세이(fabricated essays)에 해당하며, 기록 보유자는 단 하나의 "인용된" 답변에서 175개의 근거 없는 용어를 포함하고 있었습니다.
지배적인 패턴에는 이름이 필요합니다: 바로 **엔티티 완성 유출 (entity completion leakage)**입니다. 모델은 주로 _사건(events)_을 지어내는 것이 아니라, 파라미터 메모리(parametric memory)로부터 _엔티티 참조(entity references)_를 완성합니다. 이름, 실제 정체, 책 제목 등이 이에 해당합니다. 인용 규율(citation-discipline) 프롬프트 하에서 이는 명백한 위반입니다 (예를 들어, 3권 판정에서 "피터 페티그루(Peter Pettigrew)"라는 이름을 언급하는 것은, 해당 발췌문이 그 시점에서 의도적으로 숨기고 있는 정체를 주장하는 것이며, 이는 인용된 사실로 제시된 근거 없는 스포일러입니다). 또한, 이는 결과적으로 "Speak, Memory"에서 나타난 이름 빈칸 채우기 암기 프로브(name-cloze memorization probe)가 다운스트림 태스크(downstream task) 내부에서 자발적으로 발생하는 것이며, 비용 없이 측정 가능합니다.
이 감사의 부수적인 이점은 다음과 같습니다. 판정(verdict) 수준의 점수 측정으로는 절대 확인할 수 없는 샘플링 아티팩트(sampling artifacts), 즉 유창한 답변 내부에서 오타가 난 개체("Sirius" 대신 "Siris")나 공백이 압축된 토큰("apersoncouldbesplit")을 찾아냈습니다. 또한 설정 결합(config coupling)도 확인되었습니다. 정밀도(precision)를 파괴했던 광범위한 컨텍스트(wide-context) 실행이 곧 유출률(leak-rate)의 이상치(outliers)이기도 했습니다. 더 부수적인(tangential) 컨텍스트는 단순히 더 많은 답변을 생성하는 것에 그치지 않고, 근거가 부족한(less grounded) 답변을 생성하게 만듭니다.
두 가지 솔직한 주의사항이 있습니다. 첫째, 이 비율은 하한선(lower bound)입니다(의역된 유출(paraphrase leakage)은 어휘 검사(lexical check)로 포착할 수 없습니다). 둘째, 플래그가 지정된 용어들은 감사의 대상(audit candidates)일 뿐 판정(verdicts)은 아닙니다. 핵심은 후보군(candidate set)이 육안으로 확인할 수 있을 만큼 충분히 작다는 점입니다.
체크리스트
로컬 RAG를 평가하고 있으며, 다음 달에도 그 수치가 유의미하기를 원한다면 다음을 수행하십시오:
- 모든 요청에
seed를 전송하십시오. 비트 단위로 정확한(bit-exact) 실행이 필요한 경우 서빙 동시성(serving concurrency)을 1로 설정하십시오. - 모든 결과 파일에 window / top-K / max_tokens / seed / prompt-hash / model revision / repo commit 정보를 기록하십시오.
- 서버에서 모델 리비전(model revision)을 고정하십시오(
--revision <sha>). HF(Hugging Face) 캐시는 생각보다 자주main을 다시 확인(re-resolve)합니다. - 배포하는 것과 측정하는 것에 대해 정확히 하나의 프롬프트 소스(prompt source)를 유지하십시오.
- 원시 페이로드(raw payloads)를 저장하십시오. 이 포스트의 모든 주장은 테스트 프레임워크(harness)가 모든 요청과 응답을 저장했기 때문에 재검증이 가능합니다. 저장(Storage)은 존재하는 기술 중 가장 저렴한 재현성(reproducibility) 기술입니다.
한계 및 향후 계획
이것은 하나의 사례 연구(case study)입니다: 하나의 모델(9B hybrid Mamba-Transformer), 하나의 코퍼스(corpus), 20개의 적대적으로 설계된(adversarially-designed) 질문들. 암기된 코퍼스는 양날의 검과 같습니다. 유출을 감지 가능하게 만들지만, 모델이 한 번도 본 적 없는 코퍼스에서는 거부(abstention) 동작이 다를 수 있습니다. 도서들은 저작권이 있으므로 코퍼스를 배포할 수는 없습니다. 다만 파이프라인은 코퍼스 불가지론적(corpus-agnostic)이며(어떤 책/장/텍스트 CSV로도 전체 인덱스를 구축할 수 있음), 테스트 프레임워크(harness) 코드는 리포지토리(repo)가 출판을 위해 정리되는 대로 MIT 라이선스로 공개될 예정입니다.
다음 실험들은 현재의 테스트 프레임워크(harness)에서 환경 변수(env-var) 하나만으로 수행할 수 있습니다: 추론(reasoning) On/Off 어블레이션(ablation) (채팅 템플릿(chat-template)의 사고(thinking) 토글은 단답형 지연 시간(latency)을 한 자릿수 차이로 변화시킵니다), PASS에 대한 시드 앙상블(seed-ensemble) 신뢰 구간(confidence intervals), 그리고 시드(seeds) 하에서의 프롬프트 버전 A/B 테스트입니다.
동일한 환경에서 작성된 관련 포스트:
RTX 5090 1대 vs 12-GPU 클러스터
및
CPU 바운드(CPU-bound) 속편.
참고 문헌 (References)
- Longpre et al., Entity-Based Knowledge Conflicts in QA, EMNLP 2021
- Chang et al., Speak, Memory: An Archaeology of Books Known to ChatGPT/GPT-4, EMNLP 2023
- Eldan & Russinovich, Who's Harry Potter? Approximate Unlearning in LLMs, 2023
- Cuconasu et al., The Power of Noise, SIGIR 2024
- Liu et al., Lost in the Middle, TACL 2024
- Chen et al., Benchmarking LLMs in RAG (RGB), AAAI 2024
- Niu et al., RAGTruth, ACL 2024
- Chen, Zaharia & Zou, How is ChatGPT's Behavior Changing over Time?, 2023
- Thinking Machines Lab, Defeating Nondeterminism in LLM Inference, 2025
- NVIDIA, Nemotron Nano 2, 2025
AI(Claude)의 도움을 받아 초안을 작성했습니다. 모든 수치는 2026-05-24부터 2026-07-25까지의 저장된 실행 아티팩트(run artifacts: 페이로드(payloads), 결과, 메타데이터 스탬프)를 추적한 결과입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기