검색 실험 결과 (4모델 · BM25)
그래서 무엇을 알게 됐나. 측정 조건은 검색 실험 설계 (4모델·BM25)가 정본이다. 용어가 막히면 → 개념 색인
0. 3분 요약 — 세 후보는 통과했고 채택은 gpl3로 닫혔다
| 최종 구성 | 임베딩 | 리랭커 | BM25 |
|---|---|---|---|
| gpl3 + R1 ON + BM25 OFF | e5-small → gpl3 · 바뀜 | R1 ON · 그대로 | OFF · 그대로 |
바뀐 것은 임베딩 하나다.
종합 판정표 — 후보 구성·리랭커 값은 채택 모델 gpl3 기준이다
| 축 | 맞댄 것 | 결과 | MRR@10 Δ | 95% CI | 정본 |
|---|---|---|---|---|---|
| 임베딩 | e5-small ↔ bge·KURE·gpl3 | gpl3 채택 | +0.0437 | [+0.0097, +0.0807] | → §1 |
| 후보 구성 | dense만 ↔ dense+BM25 | BM25 미채택 | +0.0007 | [−0.0163, +0.0202] | → §12 |
| 리랭커 ON/OFF | 없음 ↔ R1 | R1 ON 유지 | +0.0651 | [+0.0203, +0.1108] | → §13 |
| 리랭커 종류 | R1 ↔ R2(한국어판) | R1 유지 | −0.0885 | [−0.1199, −0.0578] | → §13 |
다른 세 모델도 −0.0611 ~ −0.0897로 전부 CI 상한까지 음수다 → §13-2
결론의 세 근거
| 근거 | 무엇이 확인됐나 |
|---|---|
| 세 후보 통과 | M1·M2·M3 모두 CI 하한 > 0, 점추정 ≥ +0.03, 유형 회귀 없음 → §2 |
| 불리한 사실 | 판정을 뒤집은 것은 표본 수가 아니라 신규 82문항 구간이다 ⚠ → §3·§7 |
| 이득의 정체 | gpl3의 이득은 순위 상승이 아니라 정답 회수(recall)다 → §4·§8 |
채택은 gpl3로 닫혔다 (사용자 결정 2026-07-31). KURE-v1이 평균에서는 더 나았다. gpl3 채택 근거는 “제일 셌다”가 아니라 동일 비용에서 e5+BM25와 같은 크기의 이득을 낸다는 점이다. 따라서 BM25는 OFF 유지다.
새 발견은 임베딩이 강해질수록 리랭커의 이득이 줄어든다는 것이다. e5의 +0.1169가 gpl3에서 +0.0651로 줄고, bge·KURE에서는 CI가 0을 걸친다 → §13-3.
불리한 사실 — 구간 이질성이 채택 판단의 약한 고리다
6.8.9와 동일한 164문항으로 다시 재판정하면 M2는 +0.0282 [−0.0031, +0.0617]로 여전히 기각이다. v5/164문항의 +0.0297 [−0.0042, +0.0654]와 점추정·구간이 거의 일치한다. 판정을 뒤집은 것은 신규 82문항이다. M3는 기존 169문항에서 +0.0246(CI 0 걸침)인데 신규 82문항에서는 +0.0830이다 → §3.
0-2. 이 숫자의 범위 — 라우팅까지 잰 값이 아니다
MRR 0.7041의 전제
이 실험은 “올바른 공고가 이미 정해진 상태”에서의 청크 검색만 잰다.
| 항목 | 판정/값 |
|---|---|
| 라우팅 | 안 쟀다. 웹은 클릭, 챗/CLI는 라우터가 doc_id를 정한다 |
| 문서 안 검색 | 이것만 쟀다. C_doc MRR@10이 본문 성적의 범위다 |
| 폴백 | 라우터가 못 잡으면 전체 검색으로 넘어가지 않고 되묻는다 (spec §12-3) |
측정 조건.
| 이 실험의 조건 | 값 |
|---|---|
| 골든셋 | v6 · 준비 300 · 라벨링 254 · 채점 251 · 정답 486건 |
| 풀·검색 | 4모델 합집합 · M0 기준선 · R1 ON · C_doc · BM25 없음 |
| 통계 | paired bootstrap 95% CI · 1만 회 · seed 20260729 |
| 재표집·판정 | 95군집 / 2.64문항 · CI 하한 > 0 · 점추정 ≥ +0.03 · 유형 회귀 없음 |
BM25 축(§12)은 v7(정답 490건, 문항 수 동일)에서 C_doc ↔ G_doc으로 쟀다.
채택 조건에는 부수 지표 유의 음수 없음이 한 항 더 붙는다. 임베딩 판정은 v7에서도 같다 → §12-6.
목차
| 우선 | 절 | 제목 | 결론 |
|---|---|---|---|
| ★ | §0 | 3분 요약 | gpl3 + R1 ON + BM25 OFF |
| ⚠ | §0-2 | 범위 | 문서 안 청크 검색만 잰 값이다 |
| ★ | §1 | 성적표 | M2 평균 최고, 채택은 M3다 |
| ★ | §2 | 승격 판정 | 세 후보 전부 사전등록 기준 통과 |
| ⚠ | §3 | 구간 이질성 | 통과가 신규 82문항 구간에 몰렸다 |
| §4 | gpl3 이득 | 이득은 순위 상승이 아니라 recall이다 | |
| ★ | §5 | 채택 결정 | 동일 비용 기준으로 gpl3를 채택했다 |
| ★ | §6 | 정답지 재제작 | v5 정답지 그대로는 M3가 불리했다 |
| ⚠ | §7 | 오라벨 철회 | 판정을 뒤집은 것은 신규 문항이다 |
| ★ | §8 | recall 반증 | M3 이득은 M0가 놓친 정답 회수다 |
| §9 | 동일 엔진 | M0_exact와 M0_fast 종단 차이는 0이다 | |
| §10 | 중복 정답 | 중복 2건은 판정을 바꾸지 않았다 | |
| ⚠ | §11 | 미해결 | 남은 것은 원인 분리와 라우팅 측정이다 |
| ★ | §12 | BM25 축 | BM25는 e5 위에서만 조건을 충족했다 |
| ★ | §13 | 리랭커 축 | R2는 미채택, gpl3는 R1 ON 유지다 |
| §14 | 산출물 | 판정 스크립트와 v6/v7 산출물이 남았다 | |
| §15 | 관련 문서 | 설계·골든셋·파이프라인 문서로 이어진다 |
1부 · 판정
§2에서 멈추면 "전부 통과"만 남는다.
§3을 같이 읽어야 신규 82문항 구간이 판정을 뒤집었다는 약점이 보인다.
1. 성적표 ★ 값 정본
v6 251문항 · C_doc · 리랭커 R1 ON.
MRR@10 · nDCG@10 · R@5는 out/rescore_v6/의 모델별 재채점 덤프 aggregate가 출처이고,
R@20은 순위 덤프(ranked)에서 계산한 값이다.
| 조합 | 크기 | MRR@10 | 기준선 대비 | 95% CI | 판정 |
|---|---|---|---|---|---|
| M0 e5-small (기준선) | 118M | 0.7041 | 기준선 | — | 교체됨 |
| M1 bge-m3 | 568M | 0.7551 | +0.0511 | [+0.0103, +0.0959] ✱ | ✅ 통과 |
| M2 KURE-v1 | 0.6B | 0.7619 | +0.0578 | [+0.0210, +0.0972] ✱ | ✅ 통과 |
| M3 gpl3 (LoRA) | 118M | 0.7478 | +0.0437 | [+0.0097, +0.0807] ✱ | ✅ 통과 · 채택 |
✱ = 95% 구간이 0을 포함하지 않음 · 승격 기준 3조건 대조는 §2 · 유형별 최저치도 §2
부수 지표. 판정은 MRR@10으로만 한다(설계 §3) — 아래는 상위권 전체의 질을 함께 보려는 것이다.
| 조합 | nDCG@10 | R@5 | R@20 |
|---|---|---|---|
| M0 | 0.6387 | 0.6360 | 0.7218 |
| M1 | 0.7195 | 0.7113 | 0.8693 |
| M2 | 0.7287 | 0.7445 | 0.8680 |
| M3 | 0.7038 | 0.7085 | 0.8227 |
절대값을 v5와 나란히 놓지 않는다
v6는 정답이 353 → 486건으로 늘어 시험지 자체가 다르다. nDCG·Recall은 분모 영향을 직접 받는다 — 정답이 늘면 같은 순위표에서도 값이 내려간다. MRR은 첫 정답 순위만 보므로 상대적으로 안정하지만 역시 직접 비교 대상이 아니다. 비교는 언제나 같은 v6 기준의 모델 간 델타로 읽는다.
1-0. 코드 읽는 법 — C_doc이 뭔가
C_doc = 문서 안에서 dense로 찾고 리랭커로 재정렬 = 지금 서비스가 도는 방식.
C _ doc C = 어떤 후보 구성인가 (dense 20개, BM25 없음)
↑ ↑ doc = 어디서 찾았나 (대상 문서 안)
구성 스코프 임베딩은 여기 안 담긴다 → `M3 C_doc`처럼 앞에 붙인다
| 축 | 값 |
|---|---|
| 후보 구성 | C dense + 리랭커 (이번 실험의 유일한 구성) |
| 스코프 | _doc 문서 안 (프로덕션) |
| 임베딩 | M0 e5-small · M1 bge-m3 · M2 KURE-v1 · M3 gpl3 |
| 엔진 갈래 | M0_exact 정확 계산 · M0_fast Chroma 근사 → §9 |
기준선이 왜 두 벌인가 — M0_exact와 M0_fast
M1·M2·M3 후보는 전부 numpy 정확 계산으로 만들었다. 기존 M0 캐시는 Chroma 근사 탐색 산출물이라, 그대로 비교 상대로 쓰면 새 모델이 임베딩 성능이 아니라 검색 방식 덕에 유리해질 위험이 있었다.
그래서 M0를 정확 계산으로 한 벌 더 만들었다.
| 비교쌍 | 기준선 | 왜 |
|---|---|---|
| M3 (핵심) | M0_exact | 엔진을 맞춰 도메인 학습 효과만 남긴다 |
| M1 · M2 (6.8.9 재확인) | M0_fast | v5 판정과 같은 조건을 유지한다 |
결과적으로 두 기준선의 종단 성적은 완전히 같았다 → §9
1-1. 성적표에서 먼저 보이는 것
| 보이는 것 | |
|---|---|
| 후보 단계 회수(R@20)에서 격차가 제일 크다 | M0 0.7218 vs M1 0.8693 — +0.15 |
| 종단(MRR@10)에서는 격차가 3분의 1로 줄어든다 | +0.15 → +0.05 |
| M3는 절반 크기로 대형 모델을 따라붙는다 | R@20은 낮지만 M0와는 확실히 갈린다 |
| M1 ≈ M2 | 종단 0.7551 vs 0.7619 — 한국어 튜닝의 추가 이득은 작다 |
- M3는 절반 크기로 대형 모델을 따라붙는다 · : R@20 0.8227 — M1·M2(0.869)에 못 미치지만 M0(0.7218)와는 확실히 갈린다
리랭커가 이미 그 몫의 상당 부분을 하고 있다. 임베딩을 바꿔 얻는 것은 두 가지인데, 하나는 리랭커가 대신 못 하고 하나는 이미 하고 있다.
| 임베딩을 바꿔 얻는 것 | 리랭커가 대신해주나 | v6 실측 |
|---|---|---|
| 정답을 후보 안에 넣는 능력 | ❌ 못 함 | R@20 0.7218 → 0.8227~0.8693 |
| 후보 안에서 위로 올리는 능력 | ✅ 이미 함 | 종단 델타는 +0.04~+0.06으로 줄어듦 |
이 구조는 v5와 같다. 달라진 것은 줄어든 뒤의 값이 이번엔 유의하다는 것이다.
2. 승격 판정 — 세 후보 전부 통과
scripts/verdict_12_7.py는 판정 기준을 수치를 보기 전에 코드로 고정했다.
promote_verdict()가 세 조건을 각각 bool로 내고 all()로 묶는다 — 판정자가 끼어들 자리가 없다.
| 모델 | MRR@10 Δ | 95% CI | 유형별 최저 | 판정 |
|---|---|---|---|---|
| M2 KURE-v1 | +0.0578 | [+0.0210, +0.0972] | 직답 +0.0169 | ✅ 통과 |
| M1 bge-m3 | +0.0511 | [+0.0103, +0.0959] | 후속 −0.0100 | ✅ 통과 |
| M3 gpl3 (LoRA) | +0.0437 | [+0.0097, +0.0807] | 직답 +0.0328 | ✅ 통과 |
| 조건 | M1 | M2 | M3 |
|---|---|---|---|
| CI 하한 > 0 | ✅ +0.0103 | ✅ +0.0210 | ✅ +0.0097 |
| 점추정 ≥ +0.03 | ✅ +0.0511 | ✅ +0.0578 | ✅ +0.0437 |
| 어떤 유형도 −0.03 이하 회귀 없음 | ✅ 최저 −0.0100 | ✅ 최저 +0.0169 | ✅ 최저 +0.0328 |
2-0. 이 표를 읽는 법
| 항목 | 뜻 |
|---|---|
| Δ | 기준선(M0) 대비 문항별 차이의 평균. 클수록 좋다 |
| 95% CI | 251문항을 문서 단위로 다시 뽑아 1만 번 재계산했을 때 델타가 놓이는 구간 |
| CI가 0을 걸침 | ”같은 실험을 다시 하면 이득이 0 이하일 수도 있다” → 채택 안 함 |
| 유형별 최저 | 4개 유형 중 평균차가 가장 낮은 유형의 값 |
| 회귀 한계 −0.03 | 점추정 기준으로 작은 유형의 붕괴까지 잡는 보수적 기준 |
- 유형별 최저 · 뜻: 4개 유형(단일세부·후속·탐색비교·직답) 중 평균차가 가장 낮은 유형의 값
- 회귀 한계 −0.03 · 뜻: 점추정 기준. CI가 아니라 평균차로 본다 — 작은 유형에서 무너지는 것도 잡으려는 의도된 보수성
M1의 후속 −0.0100을 어떻게 읽나
회귀 한계는 −0.03이고 −0.0100은 그보다 크므로 판정상 통과다. 다만 세 후보 중 유일하게 음수인 유형이 있는 것은 M1이다. 유형별 최저치만 보면 순위가 M3(+0.0328) > M2(+0.0169) > M1(−0.0100)으로 평균 순위와 정반대가 된다 → §5
2-1. 유형별 전체 — 평균이 오르며 무너진 유형이 있나
| 유형 | 문항 | M1 Δ | M2 Δ | M3 Δ |
|---|---|---|---|---|
| 단일세부 | 134 | +0.0667 | +0.0687 | +0.0383 |
| 후속 | 45 | −0.0100 | +0.0507 | +0.0531 |
| 탐색비교 | 37 | +0.0371 | +0.0657 | +0.0623 |
| 직답 | 35 | +0.0846 | +0.0169 | +0.0328 |
- 회귀(−0.03 이하)는 한 칸도 없다.
회귀_유형배열이 세 비교쌍 모두 빈 리스트다 - 모델마다 약한 유형이 다르다 — M1은 후속, M2는 직답, M3는 단일세부
- 후속 유형이 갈린다. v5에서 리랭커를 켜도 +0.005밖에 안 올랐던 유형인데, M2·M3는 +0.05대를 내고 M1만 음수다. 후속은 선행 턴이 붙은 긴 질의라 모델의 긴 문맥 처리와 프리픽스 관례가 함께 작용하는 지점으로 보이나, 분리 측정은 안 했다
2-2. 부수 지표 — MRR만 보면 놓치는 것이 있나
v5의 BM25 판정에서 MRR로는 유의차 없음인데 nDCG·Recall@5로는 유의하게 나쁜 일이 있었다. 그래서 부수 지표를 같은 부트스트랩으로 함께 냈다.
| 모델 | nDCG@10 Δ | nDCG@10 95% CI | R@5 Δ | R@5 95% CI |
|---|---|---|---|---|
| M1 | +0.0808 | [+0.0405, +0.1251] | +0.0753 | [+0.0304, +0.1222] |
| M2 | +0.0900 | [+0.0532, +0.1301] | +0.1085 | [+0.0620, +0.1570] |
| M3 | +0.0651 | [+0.0283, +0.1046] | +0.0725 | [+0.0325, +0.1157] |
| 모델 | R@20 Δ | R@20 95% CI |
|---|---|---|
| M1 | +0.1476 | [+0.0962, +0.1998] |
| M2 | +0.1462 | [+0.1005, +0.1952] |
| M3 | +0.1009 | [+0.0605, +0.1434] |
네 지표가 전부 같은 방향이고 전부 CI 하한이 양수다. 반대 방향의 손해가 없다.
오히려 부수 지표가 판정 지표보다 크다
MRR@10 델타(+0.04~+0.06)보다 nDCG@10·Recall@5 델타(+0.065~+0.11)가 일관되게 크다. MRR은 첫 정답의 순위만 보고, nDCG·Recall@5는 상위권 전체를 본다. 즉 새 모델들은 첫 정답을 올리는 것보다 상위 5개 전체를 정답으로 채우는 데 더 강하다. LLM에 실제로 넘어가는 것이 상위 5개라는 점에서 이건 판정 지표보다 실사용에 가까운 개선이다. 다만 승격 판정은 사전등록대로 MRR@10으로만 한다 — 유리한 지표를 사후에 고르면 그것이 p-hacking이다.
2-3. 6.8.9 판정과의 자동 대조
compare_to_689()가 v5 판정값을 상수로 들고 있다가 하한 부호가 바뀌었는지 자동 판정한다.
| 모델 | v5/164문항 판정 | v6/251문항 | 하한 부호 변경 | 재검토 필요 |
|---|---|---|---|---|
| M1 | +0.0197 · 기각 (CI 0 걸침) | +0.0511 [+0.0103, +0.0959] | (v5 하한 미기록) | false |
| M2 | +0.0297 [−0.0042, +0.0654] · 기각 | +0.0578 [+0.0210, +0.0972] | true | true |
M2에서 플래그가 켜졌고, 산출물에 파생 작업이 그대로 찍혔다.
"파생": "6.8.9 되돌림 + ADR-010 신설 + spec.md §2 개정"
이 파생 작업은 완료됐다
막고 있던 것은 어느 모델을 올릴지가 정해지지 않았다는 것이었다. ADR과 spec은 “무엇을 쓰기로 했나”를 적는 문서라 채택 대상 없이 쓸 수 없었다. 2026-07-31 사용자 결정으로 채택 모델이 gpl3로 확정됐고, ADR-010 신설과
spec.md§2 개정도 완료됐다 → §5 · §11
3. 구간을 갈라 보면 — 이 실험의 가장 약한 고리 ⚠
§2에서 멈추면 이 결과를 잘못 쓴다. 판정은 통과했지만, 그 통과가 문항 구간에 따라 균질하지 않다.
이 절은 반박 대비가 아니라 자기 점검이다
심사위원이 물을 것을 예상해서 미리 잰 것이 아니라, 다른 논점을 검증하다 발견한 것이다. 결과가 우리에게 불리해도 먼저 제시하는 편이 낫다.
3-0. 구간이란 무엇인가
v6 251문항은 출신이 둘로 갈린다.
| 구간 | n | 무엇인가 | gold를 만든 풀 |
|---|---|---|---|
| 기존 g200 계열 | 169 | v5부터 있던 문항 | 대부분 v5의 3모델 합집합 풀 + M3 델타 추가분 |
| 신규 g300 계열 | 82 | v6에서 새로 들어온 문항 | 처음부터 4모델 합집합 풀 전건 |
gold 제작 경로가 다르다. 이 사실이 아래 모든 해석의 배경이다.
3-1. 구간별 재판정 — 전부 문서 클러스터 · 1만 회 · seed 20260729
| 구간 | n | M3 | M1 | M2 |
|---|---|---|---|---|
| 전체 | 251 | +0.0437 [+0.0097, +0.0807] ✅ | +0.0511 [+0.0103, +0.0959] ✅ | +0.0578 [+0.0210, +0.0972] ✅ |
| 기존 g200 | 169 | +0.0246 [−0.0052, +0.0550] ❌ | +0.0245 ❌ | +0.0363 [+0.0026, +0.0707] ✅ |
| 신규 g300 | 82 | +0.0830 [+0.0003, +0.1810] ✅ | +0.1058 [+0.0065, +0.2224] ✅ | +0.1022 [+0.0167, +0.2007] ✅ |
| 6.8.9 동일 164문항 | 164 | +0.0162 [−0.0120, +0.0448] ❌ | +0.0161 [−0.0158, +0.0513] ❌ | +0.0282 ❌ |
- 기존 g200 · M1: +0.0245 [−0.0094, +0.0612] ❌
- 6.8.9 동일 164문항 · M2: +0.0282 [−0.0031, +0.0617] ❌
읽어야 할 세 줄:
- 기존 169문항만 보면 M3·M1은 승격 미달이다. 전체 +0.0437은 신규 82문항의 +0.0830이 끌어올린 값이다
- 기존·신규 양쪽에서 일관되게 통과하는 유일한 모델은 M2(KURE)다
- 6.8.9와 같은 164문항에서는 셋 다 여전히 기각이다 — 이것이 §7의 근거다
그래도 판정 자체는 무효가 아니다
사전등록 규격이 전체 251문항이었고 그 기준으로 통과했다. 수치를 본 뒤에 부분집합을 골라 판정을 뒤집는 것이야말로 p-hacking이다. 구간별 수치는 판정을 취소하는 근거가 아니라 채택 판단에 얹는 정보다 → §5
3-2. 왜 신규 구간에서 델타가 큰가 — 후보 단계 Recall@20
메커니즘은 후보 단계에서 이미 갈린다.
| 구간 | M0_exact | M1 | M2 | M3 |
|---|---|---|---|---|
| 기존 g200 (169) | 0.7348 | 0.8438 | 0.8472 | 0.8177 |
| 신규 g300 (82) | 0.6949 | 0.9220 | 0.9108 | 0.8329 |
M0만 신규 문항에서 회수가 떨어진다(0.7348 → 0.6949, −0.0399). 나머지 셋은 오히려 오른다. 회수 격차(M3 − M0)는 기존 +0.0829 → 신규 +0.1380으로 벌어진다.
이것이 구간별 델타 차이의 직접 원인이다. 남는 질문은 왜 M0만 신규 문항에서 나빠지는가이며, 두 가지 설명이 경합하고 현재 데이터로 분리되지 않는다.
| # | 설명 | 근거 | 반론 |
|---|---|---|---|
| 1 | 문항 성격 — 신규 문항이 더 어렵다 | M0 MRR 신규 0.6661 vs 기존 0.7225 | 어려우면 격차가 드러나는 건 정상 |
| 2 | 라벨 제작 경로 | M0가 못 가져온 후보가 정답으로 확정될 기회가 많았다 | 그것이 6.8.8에서 의도한 정답지 편향 제거다 |
- 1 · 근거: M0 MRR 신규 0.6661 vs 기존 0.7225. 정답수/문항 1.56 vs 2.12
- 1 · 반론: 어려운 문항에서 격차가 드러나는 건 정상이다
- 2 · 근거: M0가 못 가져온 후보가 정답으로 확정될 기회가 구조적으로 많았다
- 2 · 설명: 라벨 제작 경로 — 신규 gold는 4모델 합집합 풀에서 판정됐다
심사위원은 2번을 공격할 것이다
우리 입장은 “그것이 정답지 편향 제거”이지만, 반대로 “M0에게만 불리한 잣대”로 읽을 여지가 있다. 분리하려면 신규 문항의 gold 중 M0 후보에 있던 것만으로 재채점하는 측정이 필요한데, 그 측정 자체가 M0에 유리한 잣대가 된다. 깨끗한 분리 방법이 아직 없다 → §11
3-3. 유형 분포로는 설명되지 않는다
“신규 문항이 새 모델에 유리한 유형에 몰린 것 아니냐”는 반론을 먼저 확인했다.
| 유형 | 기존 169 | 신규 82 |
|---|---|---|
| 단일세부 | 90 (53%) | 44 (54%) |
| 후속 | 30 (18%) | 15 (18%) |
| 탐색비교 | 25 (15%) | 12 (15%) |
| 직답 | 24 (14%) | 11 (13%) |
비율이 거의 같다. 유형 분포로는 이질성을 설명할 수 없다.
남는 지적: 정답수/문항이 신규가 적다(1.56 vs 2.12). 정답이 적을수록 MRR 변동폭이 커지므로 델타가 크게 나오기 쉽다는 반론이 가능하다. 이 축의 분리 측정(정답 1개 문항 vs 2개 이상)은 하지 않았다 → §11
3-4. 문서 다양성 — 신규 구간은 문서가 적다
| 구간 | 문항 | 커버 문서 | 문항/문서 |
|---|---|---|---|
| 기존 g200 | 169 | 81종 | 2.09 |
| 신규 g300 | 82 | 26종 | 3.15 |
신규 전용 문서 17종만으로 재계산하면 n=71이 되고, M3 +0.0682 [−0.0187, +0.1703] · M2 +0.0962 [−0.0111, +0.2132]로 양쪽 다 CI가 0을 걸친다.
이건 판정을 뒤집는 근거가 아니다 — n=71은 애초에 +0.03을 판정할 수 없는 표본이다 (v5가 계산한 필요 문항 수는 약 236). 다만 신규 구간의 큰 델타가 소수 문서에 의존할 가능성은 남는다. 문서 클러스터 부트스트랩이 이미 이 상관을 일부 흡수하고 있지만, 완전히 배제하지는 못한다.
3-5. 재표집 방식을 바꿔도 판정이 유지되는가
“클러스터 보정이 충분히 보수적인가”에 대한 답이다. M3 ↔ M0_exact · MRR@10:
| 재표집 단위 | 95% CI | 폭 |
|---|---|---|
| 문항 단위 | [+0.0121, +0.0776] | 0.0655 |
| 문서 클러스터 (first, 95군집) | [+0.0097, +0.0807] | 0.0710 |
| 문서집합 클러스터 (tuple, 129군집) | [+0.0102, +0.0799] | 0.0697 |
- 클러스터 보정이 CI를 8.4% 넓혔다. 방향이 맞고 크기도 설계효과(문서당 2.64문항)에 부합한다
- 어느 재표집 단위를 써도 하한이 양수로 유지된다 — 판정이 재표집 방식에 민감하지 않다
- 남는 반박: 문서 클러스터가 상관의 유일한 축인가. 같은 발주기관·같은 문서 유형의 문항도 상관이 있을 수 있다 → 미측정 (§11)
4. gpl3의 이득은 어디서 오나
핵심 비교쌍 M0 ↔ M3의 이득이 recall에서 오는지 순위에서 오는지를 순위 덤프로 분해했다.
| 구분 | 문항 | MRR@10 기여 |
|---|---|---|
| M0가 못 가져온 정답을 M3가 후보로 확보 | 58 | +0.0645 |
| 후보 내 정답 집합 동일 | 193 | −0.0208 (순위 상승 0건 / 하락 22건) |
| 합 | 251 | +0.0437 |
이득은 전부 회수(recall)에서 온다. 같은 정답을 두고 순위를 올린 효과는 오히려 음수다.
후보 단계 Recall@20을 옆에 놓으면 그림이 완성된다.
M0 0.7218 ────┐
│ M3가 메우는 구간 (+0.1009)
M3 0.8227 ────┤
│ 대형 모델이 더 가는 구간
M1 0.8693 / M2 0.8680
4-1. “M3 고유 정답 0.49%“와 모순되지 않는다 — 기준이 다르다
라벨링 단계 실측은 이랬다.
M3 고유 후보 1,020건 중 정답 5건 = 0.49%. 전체 정답 128건 중 M3 고유 기여는 3건(2.3%).
이 숫자만 보면 “M3는 새 정답을 거의 안 데려온다”로 읽힌다. 그런데 기준이 다르다.
| 숫자 | 기준 |
|---|---|
| 라벨링의 “M3 고유” | 4모델 중 M3만 데려온 후보 |
| 12.7 판정의 비교 | M0 대비 M3 |
M0가 놓친 정답 72건을 뜯어보면:
| 건수 | 비율 | |
|---|---|---|
| M1·M2도 함께 회수 | 62 | 86.1% |
| M3만 회수 | 10 | 13.9% |
M0가 놓친 정답의 86%는 M1·M2도 데려온다. 즉 “M3만의 발견”은 적지만, M0 대비로는 72건을 새로 회수한다. 두 숫자는 다른 질문에 대한 답이라 모순이 아니다.
그래서 정확한 서술은 이것이다
“gpl3가 새 정답을 발굴한다”가 아니라 “e5가 놓치던 정답을 대형 모델에 근접한 수준으로 회수한다”이다.
이 문장 하나에 도달하기까지 가설이 한 번 뒤집혔다 → §8
4-2. 순위 상승 0건 / 하락 22건을 어떻게 보나
정답 집합이 같은 193문항에서 순위가 오른 문항은 하나도 없고 22문항은 내려갔다.
이건 gpl3의 결함이 아니라 리랭커의 구조 때문으로 보인다. 후보 집합이 같으면 리랭커가 20개를 전량 재정렬하므로 dense 쪽 순위는 결과에 거의 남지 않는다. 남는 것은 후보 순서가 리랭커 동점 시 tie-break로 작용하는 잔차뿐이고, 그것은 방향이 없다.
−0.0208이라는 크기는 작지 않다. 다만 회수 이득 +0.0645가 그것을 덮는다. 후보를 넓히는 것이 순위를 다듬는 것보다 훨씬 크게 작용한다 — v5의 리랭커 실험이 말한 것과 같은 결론이다.
5. 채택 결정 — 네 축이 서로 다른 답을 가리킨다
여기서부터는 실측 결과가 아니라 제품 선택이다.
| 판단 축 | 가리키는 모델 | 근거 | 반대쪽 리스크 |
|---|---|---|---|
| 평균 성능 | M2 KURE-v1 | MRR@10 +0.0578로 최대. nDCG·R@5도 최대 | 0.6B · 1024차원 |
| 구간 견고성 | M2 KURE-v1 | 기존 169문항에서도 통과한 유일한 후보 | 신규 전용 17종은 CI가 0을 걸침 |
| 유형 안정성 | M3 gpl3 | 최저 유형도 +0.0328 | 평균은 셋 중 최저 |
| 모델 크기·운영비 | M3 gpl3 | 118M · 384차원 | 회수(R@20 0.8227)는 대형 모델보다 낮다 |
- 평균 성능 · 반대쪽 리스크: 0.6B · 1024차원 — 인덱스·질의 비용이 크다
- 구간 견고성 · 근거: 기존 169문항에서도 통과한 유일한 후보 (+0.0363 [+0.0026, +0.0707])
- 유형 안정성 · 근거: 최저 유형도 +0.0328 — 세 후보 중 유일하게 전 유형 +0.03 이상
- 모델 크기·운영비 · 근거: 118M · 384차원 — M1·M2 대비 약 5배 작다. 인덱스 크기·질의 비용이 그만큼 낮다
설계 §5의 규칙은 “여러 구성이 통과하면 작은 쪽을 고른다”이고, 그 규칙은 gpl3를 가리킨다. 그런데 그 규칙은 *“통과한 것들의 성능이 실질적으로 같을 때”*를 상정하고 쓰인 것이고, 이번엔 견고성 축이 명확히 갈린다.
채택 확정 — gpl3 (M3) · 사용자 결정 2026-07-31
네 축이 서로 다른 답을 냈고, 사용자가 축의 우선순위를 정해 gpl3로 닫았다. 근거는 셋이다.
# 근거 1 설계 §5의 명문 규칙 — “여러 구성이 통과하면 작은 쪽”. gpl3는 118M·384차원으로 KURE의 1/5 2 유형별 최저값이 가장 높다 — gpl3 +0.0328 > KURE +0.0169 > bge −0.0100. 무너지는 유형이 없다 3 gpl3 단독(+0.0437) ≈ e5+BM25(+0.0430)인데 BM25 인덱스가 필요 없다 — 파인튜닝이 BM25가 메우던 구멍을 더 싸게 메운다 → §12 3번이 이번에 새로 들어온 정보다. BM25 재측정 전에는 “e5에 BM25를 얹는 값싼 길”이 비교 대상으로 남아 있었는데, 그 길의 이득이 gpl3 단독과 같은 크기라는 것이 실측으로 나왔다. 같은 이득이면 인덱스를 하나 더 들지 않는 쪽이 싸다.
지우지 않는 사실 — 평균과 견고성에서는 KURE가 앞섰다
채택이 정해졌다고 이 표의 KURE 열이 약해지는 것은 아니다.
- 평균 성능 최고: MRR@10 +0.0578 · nDCG@10 +0.0900 · Recall@5 +0.1085 전부 KURE가 최대
- 구간 견고성 유일 통과: 기존 169문항에서 승격 기준을 통과한 유일한 모델이 KURE다. gpl3는 그 구간에서 +0.0246, CI 0 걸침이다 → §3
즉 이 결정은 “gpl3가 더 낫다”가 아니라 “같은 급의 이득이면 작은 쪽”이라는 규칙의 적용이다. 그래서 아래를 재판정 조건으로 남긴다.
재판정 조건 왜 기존 g200 구간의 gpl3 델타가 표본 확대 후에도 CI 0을 걸치면 견고성 축의 KURE 우위가 굳어진다 운영에서 인덱스 크기·질의 비용이 제약이 아니게 되면 근거 1(작은 쪽)의 전제가 사라진다 유형별 최저값 우위가 다른 골든셋에서 재현되지 않으면 근거 2가 표본 특이성이었다는 뜻이다 bge·KURE를 채택 모델로 되돌려 보게 되면 두 모델에서는 리랭커 ON 이득의 CI가 0을 걸친다. 리랭커는 질의마다 비용이 드므로 OFF가 선택지가 될 수 있다 → §13 ADR-010 신설과
spec.md§2 개정은 이 결정을 근거로 완료됐다 → §11
결정에 필요한 정보를 한 표에 모으면
| M1 bge-m3 | M2 KURE-v1 | M3 gpl3 | |
|---|---|---|---|
| 파라미터 | 568M | 0.6B | 118M |
| 차원 | 1024 | 1024 | 384 |
| MRR@10 (절대) | 0.7551 | 0.7619 | 0.7478 |
| MRR@10 Δ | +0.0511 | +0.0578 | +0.0437 |
| CI 하한 | +0.0103 | +0.0210 | +0.0097 |
| R@20 | 0.8693 | 0.8680 | 0.8227 |
| 유형별 최저 | −0.0100 | +0.0169 | +0.0328 |
| 기존 169문항 구간 | +0.0245 ❌ | +0.0363 ✅ | +0.0246 ❌ |
| 신규 82문항 구간 | +0.1058 ✅ | +0.1022 ✅ | +0.0830 ✅ |
| 리랭킹 소요(251문항) | 34.2s | 31.7s | 28.0s |
2부 · 실험하며 바로잡은 것
여기부터는 결론이 아니라 경위다. 결론만 필요하면 1부에서 멈춰도 된다. 다만 아래 다섯은 수치보다 오래 남을 기록이다 — 대부분 “에러 없이 조용히 틀리는” 종류였고, 둘은 우리가 먼저 세웠던 설명을 스스로 철회한 것이다.
6. 정답지를 4모델 기준으로 다시 만들어야 했다 ★
결론: M3를 v5 정답지에 그냥 얹었으면 M3만 데려온 후보가 전부 오답으로 세어졌다. 정답지부터 고쳤다.
| 용어 | 이 절에서 뜻하는 것 |
|---|---|
| v5 정답지 | 3모델(M0·M1·M2) 합집합 풀에서 판정된 라벨. 정답 353건 |
| v6 정답지 | 4모델 합집합 풀에서 판정된 라벨. 정답 486건 |
| 델타 라벨링 | 기존 문항에서 새 모델이 새로 데려온 후보만 판정하는 방식 |
| 미판정 후보 | 사람이 아직 정답/오답을 읽어보지 않은 후보. 그대로 두면 오답처럼 세어진다 |
- 미판정 후보 · 이 절에서 뜻하는 것: 사람이 아직 정답/오답을 읽어보지 않은 후보. 그대로 두면 오답처럼 세어진다
6-1. 무엇이 문제였나
M3만 데려온 후보 → v5 풀에 없음 → 라벨 없음 → 채점기가 오답으로 셈
M3가 진짜 정답을 새로 찾아올수록 점수가 깎인다. 잘할수록 손해를 보는 구조다.
이건 이미 겪은 사고와 구조가 같다 (6.8.8) v4 정답지는 e5 후보만 판정해서 만들어졌고, 그 결과 새 모델이 사용자에게 보여줄 상위 10개 중 26~32%를 아무도 읽어본 적이 없었다(e5는 0.7%).
조합 상위 10개 중 미판정 (v4 기준) e5 + R1 0.7% bge-m3 + R1 26.0% KURE + R1 32.0% v5의 3모델 풀링이 이걸 걷어내 전 조합 0.0%로 만들었다. 모델을 하나 추가하는 순간 같은 구멍이 다시 열린다. 이번엔 사고가 나기 전에 막았다는 점이 다르다.
6-2. 어떻게 고쳤나 — 두 갈래
| 갈래 | 대상 | 무엇을 판정했나 | 규모 |
|---|---|---|---|
| 신규 85문항 | v6에 새로 들어온 문항 | 4모델 합집합 후보 전건 | 후보 4,215개 |
| 기존 169문항 델타 | v5부터 있던 문항 | M3 고유 신규 후보만 | M3 고유 598건 |
- 신규 85문항 · 규모: 후보 4,215개 · 문항당 평균 49.6
- 기존 169문항 델타 · 무엇을 판정했나: M3 고유 신규 후보만 (기존 정답은 맥락으로 동봉)
기존 문항을 전건으로 다시 안 읽은 이유가 중요하다. 비용 때문만이 아니다 — 다시 읽으면 잣대가 미세하게 달라져 기존 라벨과 어긋난다. 기존 정답은 v5 잣대로, 신규 후보만 새로 판정하는 것이 잣대를 하나로 유지하는 유일한 방법이었다.
6-3. 2단계 라벨링 — 왜 스킬을 안 바꿨나
- 라벨러 —
r8/SKILL.md(v8, 재현율 0.863 / 정밀도 0.750)로 원문 대조 판정 - 독립 검증자 —
VERIFY_SPEC.md규격으로 라벨러가 고른 정답만 재검사
여기서는 절대 정확도보다 일관성이 중요하다. 증분을 더 엄격한 방법으로 라벨링하면 기존 정답과 잣대가 갈려, M0(기존 라벨 위주)와 M3(신규 라벨 위주)가 서로 다른 기준으로 채점된다. 지금 고치려는 편향을 다른 편향으로 바꾸는 꼴이다.
6-4. 검증 결과가 스킬 프로필과 방향이 맞았다
| 갈래 | 문항 | 검증 verdict |
|---|---|---|
| 신규 85문항 | 85 | 통과 77 · 수정 8 |
| 기존 169문항 델타 | 155 | 통과 155 · 수정 0 |
| 갈래 | 유지 | 제거 | 추가 | 보류 |
|---|---|---|---|---|
| 신규 85문항 | 120 | 2 | 8 | 0 |
| 기존 169문항 델타 | 5 | 0 | 0 | 1 |
제거 2건보다 추가 8건이 많다. 이게 왜 “설계대로 작동했다”의 증거인가:
라벨러 스킬 프로필은 재현율 0.863 / 정밀도 0.750이다.
| 프로필 | 뜻 | 검증에서 나타날 모습 |
|---|---|---|
| 정밀도 0.750 | 고른 것 중 25%는 오답 | 제거가 나온다 |
| 재현율 0.863 | 진짜 정답의 14%를 놓친다 | 추가가 나온다 |
검증자가 라벨러가 놓친 것까지 주워 담으면 추가 > 제거가 된다. 실측이 그 방향과 일치했다. 반대로 나왔다면(제거가 훨씬 많았다면) 라벨러가 프로필과 다르게 동작하고 있다는 신호였을 것이다.
신규 85문항 최종 정답은 라벨러 122건 → 검증 후 128건이 됐다.
확정된 판정 기준 2건이 검증자 간에 공유됐다.
① 기준 V-1(발주기관) = 역할 정의가 명시된 청크만 인정('한국철도공사(이하 「발주기관」)').
표지 기관명 단독·서식 서명란은 제외.
② 식별자 조각 오염 = 값 조각이 자기완결이면 R8 식별자 짝은 오염으로 본다.
보류 1건은 미반영(정답으로 치지 않는다) — 오염보다 누락이 덜 해롭기 때문이다.
팀 OpenAI API 미사용 골든셋 판정·라벨링은 전부 Claude가 직접 수행했다(CLAUDE.md CRITICAL · spec §10-18).
judge=gpt-5-nano경로는 골든셋에 한 번도 돌리지 않았다.
7. “표본이 늘어 판정이 뒤집혔다”는 설명을 철회했다 ★
결론: 처음 적었던 설명은 부정확하다. 판정을 뒤집은 것은 표본 수가 아니라 신규 문항 구간이다.
판정 직후 이렇게 적었다. 앞 문장은 맞고 뒤 문장은 과했다.
| 처음 적었던 서술 | 지금 판정 |
|---|---|
| 6.8.9의 “전 축 현행 유지”는 결론이 틀렸던 게 아니라 표본 부족으로 판정이 성립하지 않았던 것이다 | ✅ 유지 |
| §6.8 측정 해상도 분석(+0.03 판정에 236문항 필요)이 정확히 예측한 대로다 | ❌ 철회 — 표본 수가 아니라 신규 문항 구간이 판정을 움직였다 |
검증하려고 같은 164문항으로 재판정해 봤다.
| 비교 | M2 (KURE) |
|---|---|
| v5/164문항 원 판정 | +0.0297 [−0.0042, +0.0654] · 기각 |
| v6 정답지로 같은 164문항 재판정 | +0.0282 [−0.0031, +0.0617] · 여전히 기각 |
점추정도 구간도 거의 일치한다. 정답지가 v5 → v6로 바뀌었는데도 그렇다.
즉 “같은 164문항에서는 표본을 아무리 정밀하게 재도 여전히 기각”이다. “문항이 늘어 통계 검정력이 좋아졌다”만으로는 틀린 설명이다.
판정을 뒤집은 것은 신규 82문항이다.
| 구간 | M3 | M2 |
|---|---|---|
| 기존 g200 (169) | +0.0246 (CI 0 걸침) | +0.0363 |
| 신규 g300 (82) | +0.0830 | +0.1022 |
신규 구간의 델타가 기존의 3~4배다.
이 사실은 우리 결론에 불리하다 — 그래서 먼저 낸다 “표본을 늘렸더니 통과했다”는 깨끗한 서사고, “새로 넣은 문항에서만 크게 이겼다”는 훨씬 방어하기 어려운 서사다. 후자가 사실이다.
심사위원이 물었을 때 그 자리에서 답이 나와야 하고, 결과가 불리해도 우리가 먼저 제시하는 편이 낫다. 나중에 지적당하면 은폐로 읽힌다.
7-1. 그래도 방어 가능한 부분
| 반론 | 답 |
|---|---|
| 쉬운 문항으로 델타를 부풀린 것 아닌가 | 반대다. 신규 문항이 더 어렵다 — M0 MRR 0.6661 vs 기존 0.7225 |
| 기존 문항에서 왜 델타가 작나 | 기존 문항은 M0 기준선이 이미 0.72로 높아 개선 여지가 작다(천장 효과) |
| 유형 분포 차이 때문 아닌가 | 유형 분포가 거의 같다 — 단일세부 53% vs 54%, 후속 18% vs 18% (§3-3) |
- “쉬운 문항으로 델타를 부풀린 것 아닌가” · 반론: “쉬운 문항으로 델타를 부풀린 것 아닌가”
- “그럼 왜 어려운 문항에서 격차가 큰가” · 반론: “그럼 왜 어려운 문항에서 격차가 큰가”
- “유형을 새 모델에 유리하게 짠 것 아닌가” · 반론: “유형을 새 모델에 유리하게 짠 것 아닌가”
7-2. 그래도 남는 질문 — 분리되지 않았다
두 구간의 델타 차이가 문항 성격 때문인지 라벨 제작 경로 때문인지 분리되지 않았다.
- 신규 85문항의 gold는 4모델 합집합 풀에서 판정됐다
- 기존 문항의 gold 대부분은 v5(3모델) 풀에서 나왔다
- 따라서 신규 구간에서는 M0가 못 가져온 후보가 정답으로 확정될 기회가 구조적으로 많았다
우리 입장은 “그것이 6.8.8에서 의도한 정답지 편향 제거”다. 반대 입장은 “M0에게만 불리한 잣대”다. 현재 데이터로는 어느 쪽도 배제할 수 없다 → §11
덧붙여 확인한 것: 기존 문항에서 v6로 추가된 정답은 2건뿐이다(델타 판정 실측). 정답 증가분 133건 중 나머지는 전부 신규 85문항의 gold다. 즉 기존 169문항의 채점 잣대는 v5와 사실상 동일하다. 그런데도 그 구간의 M2 델타가 +0.0363으로 v5/164문항의 +0.0297보다 높다 — 라벨 확장이 아니라 문항 수 증가가 작용한 흔적이다.
8. “gpl3의 이득은 순위 상승”이라는 가설을 실측으로 반증했다 ★
결론: 라벨링 단계에서 세운 해석은 틀렸다. 이득은 recall에서 온다.
8-1. 무엇을 근거로 그렇게 봤나
라벨링 실측이 이랬다.
| 실측 | 값 |
|---|---|
| M3 고유 후보 | 1,020건 |
| 그중 채택된 정답 | 5건 = 0.49% |
| 전체 정답 128건 중 M3 고유 기여 | 3건 = 2.3% |
그런데 12.2a에서 Recall@20이 +0.0697 올랐다(v5/164문항·리랭커 OFF 기준).
여기서 이렇게 추론했다. 세 단계 모두 실측에서 무너졌다.
| 처음 적었던 추론 | 실측 |
|---|---|
| M3 고유 후보의 정답률 0.49%는 6.8.8 선례(1.3%)보다 낮다 | ❌ 두 “고유”의 기준이 달라 비교가 성립하지 않는다 → §8-2 |
| 새 청크를 데려온 게 아니라 이미 정답인 청크를 상위 20 안으로 끌어올린 것이다 | ❌ 순위 덤프를 분해하니 순위 상승 0건 · 하락 22건이다 → §8-3 |
| 따라서 gpl3의 이득은 새 정답 발굴이 아니라 순위 상승이다 | ❌ 정반대다. 이득은 M0가 놓친 정답의 회수(recall)다 → §8-3 |
이 문장은 scripts/verdict_12_7.py의 docstring과 verdict_12_7.json의 해석_주의 배열에
그대로 남아 있다. 산출물이라 지우지 않았고, 이 문서가 정정 기록이다.
8-2. 무엇이 문제였나 — 두 “고유”의 기준이 다르다
| 숫자 | 무엇을 세는가 |
|---|---|
| 라벨링의 “M3 고유 0.49%“ | 4모델 중 M3만 데려온 후보의 정답률 |
| 12.7 판정 | M0 대비 M3 |
M0가 놓친 정답을 M1·M2와 함께 M3가 회수한 경우는 “M3 고유”에 안 잡힌다. 그리고 실제로 그게 대부분이었다.
| M0가 놓친 정답 72건 | 건수 | 비율 |
|---|---|---|
| M1·M2도 함께 회수 | 62 | 86.1% |
| M3만 회수 | 10 | 13.9% |
“M3만의 발견은 적다”와 “M0 대비로는 많이 회수한다”가 동시에 참이다. 두 숫자를 같은 얘기로 읽은 것이 오류의 정체다.
8-3. 어떻게 확인했나 — 순위 덤프 분해
251문항을 두 갈래로 갈라 MRR@10 기여를 분해했다.
| 구분 | 문항 | 기여 |
|---|---|---|
| M0가 못 가져온 정답을 M3가 후보로 확보 | 58 | +0.0645 |
| 후보 내 정답 집합 동일 | 193 | −0.0208 |
| 합 | 251 | +0.0437 |
정답 집합이 같은 193문항에서 순위 상승은 0건, 하락은 22건이다. 가설이 참이었다면 이 칸이 양수여야 했다. 정확히 반대 부호가 나왔다.
교훈 — 지표가 오른 것을 보고 메커니즘을 추론하면 안 된다 “Recall@20이 올랐는데 고유 정답이 적다 → 순위를 올렸을 것”은 그럴듯한 추론이다. 그리고 그럴듯한 설명을 찾으면 탐색이 거기서 멈춘다. 실제로 필요했던 것은 추론이 아니라 순위 덤프를 열어 문항별로 분해하는 것이었고, 그건 수 분이면 되는 작업이었다.
v5에서도 같은 실패가 있었다 —
C ≡ D를 관찰하고 “동점 정렬 부작용”으로 설명이 끝나버려 그 아래의 “D는 애초에 C와 같다”는 사실에 도달하지 못했다. 증상에 맞는 설명을 찾으면 탐색을 멈추게 된다.
8-4. 그래서 남길 최종 서술
gpl3는 e5가 놓치던 정답을 대형 모델에 근접한 수준으로 회수한다.
“새 정답을 발굴한다”도 아니고 “순위를 올린다”도 아니다.
라벨링에서 회수된 청크의 성격도 이 서술과 맞는다 — M3가 회수한 3건은 전부 ::0008이고
표가 아니라 「1. 사업개요」 절(사업명·사업기간·예산·수요기관·입찰방식)이었다.
6.8.8에서 3모델 전환 때 회수분이 표 형태 세부내역에 집중됐던 것과 다른 패턴이며,
GPL로 RFP 도메인에 적응해 정형 섹션을 잡는다는 해석과 방향이 맞는다.
9. 동일 엔진 게이트가 종단에서는 무의미했다 — 그러나 사전에는 알 수 없었다
결론: M0를 두 벌 만든 것은 결과적으로 불필요했다. 그 사실은 측정으로만 알 수 있었다.
| 용어 | 이 절에서 뜻하는 것 |
|---|---|
M0_fast | Chroma 근사 탐색으로 만든 M0 후보 캐시 (기존 계보) |
M0_exact | numpy 정확 계산으로 만든 M0 후보 캐시 |
| 동일 엔진 게이트 | 모든 모델의 후보를 같은 검색 방식으로 만들라는 통제 조건 |
9-1. 왜 게이트를 걸었나
v5에서 실측된 사실이 있었다.
Chroma는
doc_id필터를 걸어도 전수 비교가 아니라 근사 탐색이라, 정확 계산과 대조하니 164문항 중 9문항(5.5%)에서 상위 20이 달랐다.
M1·M2·M3 후보는 전부 numpy 정확 계산으로 만들었다. M0만 근사 캐시를 쓰면 새 모델이 임베딩 성능이 아니라 검색 방식 덕에 유리해질 수 있다. 그래서 M0를 정확 계산으로 한 벌 더 만들고, 핵심 비교쌍(M3)의 기준선으로 삼았다.
9-2. 실측 — 완전히 씻겼다
| 비교 | 종단 MRR@10 차이 |
|---|---|
M0_exact ↔ M0_fast | 0.000000 (251문항 전건) |
후보 집합이 다른 문항이 있었지만, 교체된 청크가 전부 비정답이고 상위권 밖이었다.
리랭커가 후보 20개를 전량 재정렬하기 때문이다. dense가 어떤 순서로 무엇을 데려왔든, 리랭커가 다시 줄세우면 그 차이는 비정답 청크의 자리바꿈으로만 남는다.
설계에 남길 사실 동일 엔진 게이트는 dense 단독 측정에서만 구속력이 있고, 리랭커 종단에서는 씻긴다. 이건 이번 실험이 새로 알아낸 것이다.
9-3. 그래도 낭비가 아닌 이유
게이트를 안 걸고 “씻겼을 것”이라 말하는 것과, 걸고 나서 “씻겼다”고 말하는 것은 다르다 사전에는 알 수 없었다. 유리한 편향이 생길 위험을 배제할 근거가 없었고, 실제로 v5에서 5.5% 문항의 상위 20이 갈린다는 실측이 있었다.
비용은 M0 캐시 한 벌 + 리랭킹 25초였다. 그 값으로 “판정이 검색 엔진 덕이 아니다”를 증명했다.
10. 병합 시 중복 정답 2건을 잡았다
결론: 델타 라벨을 리스트로 이었으면 분모가 부풀어 채점이 조용히 틀어질 뻔했다. 집합 연산으로 해소했다.
10-1. 무엇이 문제였나
기존 169문항 델타 라벨링에서 신규 정답 5건이 나왔는데, 그중 2건이 이미 v6 라벨에 있던 청크였다.
g200:37cd7e06 :: 0011 ← 이미 있음
g200:9f071cc8 :: 0009 ← 이미 있음
원인: 델타 생성 코드가 “기존 판정분 제외”를 할 때 v5(164문항) 라벨을 기준으로 삼았다. 그런데 12.5에서 기존 문항 5건에 라벨이 새로 붙어 있었고, 그 5문항이 v5 기준에는 없다. 그래서 이미 정답인 청크가 “미판정 후보”로 다시 나왔다.
10-2. 그대로 뒀으면 무엇이 틀어졌나
| 병합 방식 | 결과 |
|---|---|
| 리스트 append | 같은 chunk_id가 gold_chunk_ids에 두 번 들어간다 |
| → | 그 문항의 nDCG·Recall 분모가 부푼다 |
| → | 정답을 다 맞혀도 Recall이 1.0에 못 미친다 |
| → | 에러 없이 그 문항 점수만 낮게 나온다 |
집합 연산으로 합쳐 해소했다. 최종 v6 정답은 486건이다.
교훈 — 라벨 병합은 항상 집합 연산으로 이 실패는 두 단계가 서로 다른 기준을 참조해서 생겼다 (델타 생성은 v5 기준, 병합 대상은 v6 라벨). 어느 쪽도 틀린 코드가 아니었고, 연결부에서 어긋났다. 집합 연산은 그 어긋남을 무해하게 흡수한다 — 리스트는 그대로 통과시킨다.
10-3. 이 중복이 M3 기여도 해석에도 영향을 줬다
델타 판정 직후 “신규 정답 5건”으로 읽으면 M3 고유 후보 598건 중 0.84%가 된다. 중복 2건을 빼면 실제 신규 정답은 3건 / 598 = 0.50%다.
그 전에 이 수치로 M3 효과를 읽으면 과대평가된다. 영향 문항은 2건뿐이라 판정 결과는 안 바뀌었지만, 해석 단계에서는 실제로 잘못 읽힐 뻔했다.
같은 병합 작업에서 MANIFEST 기록 2건도 정정했다.
① 「풀 구성 모델」 열 신설 — v3·v4는 e5 단독, v5는 3모델, v6는 4모델로 서로 다른데
그 칸이 없어 6.8.8 미판정 편향 사고가 났다. 다시 밟지 않도록 열을 만들었다.
② g200:37cd7e06 결함 기록 취소선 정정 — “정답이 시스템 구성도 이미지 안에만 있어
어떤 검색으로도 못 맞힌다”고 기록돼 있었으나, 실측하니 본문 표에 텍스트로 존재한다.
hwp5 표 추출 수정(371980d 중첩 표 · fb928c7 병합 셀 보존)으로 복원된 것이고,
v6에서 M3 델타 판정으로 같은 청크가 재확인됐다. 정상 채점 대상이다.
3부 · 남은 것
11. 미해결
결론: 남은 것은 성능 판정 문제가 아니라 원인 분리와 라우팅 측정이다. 승격 판정 자체는 바뀌지 않는다.
| 남은 항목 | 현재 상태 | 다시 봐야 하는 조건 |
|---|---|---|
| 채택 모델 | ✅ gpl3 확정 (사용자 결정 2026-07-31) | §5의 재판정 조건 4건 중 하나가 성립할 때 |
| ADR-010 신설 | ✅ 완료 — gpl3 채택 결정 반영 | — |
| ADR-011 신설 | ✅ 완료 — BM25 OFF 유지, 단 근거 정정 | — |
spec.md §2 임베딩·BM25 제약 개정 | ✅ 완료 — gpl3 채택 · BM25 OFF 유지 반영 | — |
골든셋 current → v7 이동 | ✅ 완료 — /srv/GPTPilot/goldenset/current → v7_20260731 | — |
| 신규/기존 구간 이질성의 원인 분리 | 미측정 — 문항 성격 vs 라벨 제작 경로 | 깨끗한 분리 설계가 나올 때 |
| 다른 클러스터 축 (발주기관·문서 유형) | 미측정 | 상관 축을 더 보수적으로 잡을 필요가 생길 때 |
| 정답수 1개 / 2개 이상 분리 | 미측정 | ”정답이 적으면 변동폭이 크다”를 검증할 때 |
| BM25 축 | ✅ 판정 완료 — e5 위에서만 조건 충족, gpl3라 OFF 유지 | 기준 임베딩이 e5 계열로 되돌아갈 때 |
| 리랭커 축 | ✅ 판정 완료 — R2 미채택, gpl3에서는 R1 ON 유지 | bge·KURE를 채택 모델로 되돌려 볼 때 |
M0·G_doc nDCG 부호 역전의 원인 | ✅ 철회 — 전제 자체가 오라벨이었다 → §12-7 | — |
| 라우팅 단계의 임베딩 교체 효과 | 미측정 — 이 실험은 전부 문서 스코프다(§0-2) | 라우팅 포함 측정을 돌릴 때 |
라우팅은 gpl3가 문서 선택까지 바꾸는데 그 효과를 안 쟀다는 뜻이다 — 재인덱싱 후 종단 측정이 필요하다. nDCG 부호 역전은 −0.0364가 M0가 아니라 KURE의 값이어서 생긴 착시였고, M0의 v5 부수 지표는 기록이 없어 대조 자체가 불가능하다 → §12-7
11-1. 원인 분리가 왜 어려운가
§3-2의 두 설명(문항 성격 vs 라벨 제작 경로)을 가르려면 신규 문항의 gold 중 M0 후보에 있던 것만으로 재채점하면 된다.
그런데 그 측정 자체가 M0에 유리한 잣대가 된다. “M0가 데려온 것만 정답으로 친다”는 것은 v4 정답지가 가졌던 편향 그 자체이기 때문이다.
즉 편향을 재기 위해 편향된 잣대를 써야 하는 구조라, 이 축은 단일 측정으로 닫히지 않는다. 현재로서는 양쪽 설명을 병기하고 어느 쪽도 배제하지 않는 것이 정직한 상태다.
11-2. 승격 판정과 채택 결정을 섞지 않는다
| 무엇을 말하나 | 상태 | |
|---|---|---|
| 승격 판정 | ”올릴 수 있다” | ✅ 세 후보 통과 (사전등록 규격) |
| 채택 결정 | ”무엇을 올린다” | ✅ gpl3 (사용자 결정 2026-07-31) |
두 칸이 다 찼다고 같은 근거로 찬 것은 아니다. 왼쪽은 사전등록된 통계 규격이 정했고, 오른쪽은 규칙(작은 쪽) + 비용 판단이 정했다. 그래서 오른쪽만 재판정 조건을 달고 있다(§5).
사후에 부분집합을 골라 승격을 취소하면 p-hacking이다. 사전등록 규격은 전체 251문항 · 문서 클러스터 부트스트랩 · MRR@10이었고, 그 기준에서 세 후보 모두 통과했다. 구간별 수치(§3)는 판정을 취소하는 근거가 아니라 채택 판단에 얹는 정보다.
12. BM25 축 — 재측정 완료 · e5 위에서만 뒤집혔다
결론: v5의 BM25 미채택은 판정이 틀렸던 게 아니라 성립하지 않았던 것이다. 그런데 조건을 충족한 것은 e5 줄 하나뿐이고, 채택 모델은 e5가 아니다 → BM25는 OFF 유지.
12-1. 왜 다시 쟀나
v5는 BM25를 미채택으로 닫았다. 그런데 그 판정에는 임베딩 판정을 뒤집은 것과 똑같은 해상도 부족이 있다.
| v5/164문항 기준 | 값 |
|---|---|
e5 줄 C_doc → G_doc | +0.0222, CI [−0.0184, +0.0642] |
| bge-m3 | −0.0035 |
| KURE-v1 | −0.0127 |
e5의 +0.0222는 KURE의 +0.0297과 같은 성격의 숫자다 — 점추정은 이득 쪽인데 CI가 0을 걸쳐 기각된 것. 그리고 그 KURE는 v6에서 하한 부호가 바뀌어 통과했다.
한쪽만 다시 재면 그것이 편향이다 “임베딩은 표본이 부족해서 판정이 안 섰다”고 인정했으면, 같은 표본으로 기각된 BM25에도 같은 말을 해야 한다. 임베딩만 재측정해 통과시키고 BM25는 옛 판정을 유지하는 것은 우리에게 유리한 축만 다시 재는 것이다.
12-2. 재측정이 성립하기 위해 먼저 한 일 — 미판정 편향 ★
이 절을 안 읽으면 아래 판정을 신뢰할 수 없다. 그냥 쟀으면 BM25는 구조적으로 졌다.
BM25 전용 후보 2,819건 중 734건(26.0%)에 판정 이력이 없었다(기존 169문항 중 118문항에 걸침). 미판정 후보는 채점기가 오답으로 센다.
| 변형 | 리랭커 입력 기준 미판정률 |
|---|---|
C_doc (BM25 없음) | 0.6% |
E_doc | 3.3% |
F_doc | 6.4% |
G_doc (판정 대상 정본) | 8.6% |
하필 판정 대상 변형이 가장 큰 페널티를 받는다. 그리고 이건 “원래 다들 그렇다”가 아니다.
| dense 대조군 미판정률 | M0 | M1 | M2 | M3 |
|---|---|---|---|---|
| 0.56% | 0.72% | 0.82% | 0.00% |
dense 쪽은 전부 1% 미만이다. 26.0%는 BM25에만 걸린 불이익이다.
원인은 스코프가 바뀐 것이다. 기존 164문항 풀의 어휘 후보는 전역 BM25로 만들어졌고 (그 후보의 32.6%가 애초에 대상 문서 밖이었다 → 12-8), 이번 측정은 문서 스코프 BM25다. 대상 문서 안에서 새로 올라온 청크는 판정받은 적이 없다.
조치 — 734건 전건을 라벨링했다.
| 단계 | 규모 | 결과 |
|---|---|---|
| 라벨링 | 118문항 · 734건 · 라벨러 8명 병렬 | 정답 4건 = 0.54% |
| 독립 검증 | 검증자 2명 | 추가 0 · 제거 0 |
| 리드 자동 재현율 감사 | 변별 토큰 60% 이상을 담고도 기각된 후보 탐색 | 0건 |
- 리드 자동 재현율 감사 · 규모: 기준답변 변별 토큰 60% 이상을 담고도 기각된 후보 탐색
편향의 실제 크기는 정답 4건이었다 26.0%라는 미판정률은 컸지만, 그 안에서 실제로 정답이었던 것은 4건뿐이다. 즉 라벨링을 안 했어도 판정은 아마 같았을 것이다. 그러나 그건 재고 나서야 할 수 있는 말이다 — 재기 전에는 734건이 전부 오답으로 세어지는 상태였고, 그 상태의 측정은 “BM25가 진 건 판정을 안 해줘서”라는 반박을 막을 수 없다.
지금은 막혔다. 이 라벨링의 산출물은 정답 4건이 아니라 반박 봉쇄다. 구조는 6.8.8(정답지 편향 제거)·§6과 같고, 이번에도 사고가 나기 전에 막았다.
12-3. 판정 결과 ★ 값 정본
v7 251문항 · 리랭커 R1 ON · 문서 클러스터 95군집 · 1만 회 · seed 20260729.
C_doc ↔ G_doc (dense에 BM25를 빼지 않고 더하기만 한 구성).
| 모델 | MRR@10 Δ | 95% CI | nDCG@10 Δ | Recall@5 Δ | 판정 |
|---|---|---|---|---|---|
| M0 e5-small | +0.0430 | [+0.0032, +0.0869] | +0.0459 | +0.0548 | ✅ 채택 조건 4/4 충족 |
| M1 bge-m3 | +0.0016 | [−0.0141, +0.0182] | −0.0145 | +0.0036 | ❌ 미채택 |
| M2 KURE-v1 | −0.0027 | [−0.0256, +0.0202] | −0.0232 | −0.0290 | ❌ 미채택 |
| M3 gpl3 | +0.0007 | [−0.0163, +0.0202] | −0.0135 | −0.0108 | ❌ 미채택 |
-
M0 e5-small · nDCG@10 Δ: +0.0459 [+0.0048, +0.0902]
-
M0 e5-small · Recall@5 Δ: +0.0548 [+0.0091, +0.1022]
M0의 유형별 델타(회귀 없음): 직답 +0.0930 · 탐색비교 +0.0623 · 단일세부 +0.0335 · 후속 +0.0165. Recall@20도 +0.0955 [+0.0480, +0.1432]로 유의 양수다.
E_doc·F_doc은 전 모델 미채택이다. M0 줄에서도 E_doc +0.0255(CI 0 걸침) ·
F_doc +0.0377(하한 −0.0018)로 G_doc만 하한이 양수로 넘어갔다.
그래서 무엇을 말할 수 있나 — 한 줄로 BM25의 이득은 dense가 약할 때의 보완이고, 12.7에서 승격한 세 임베딩 위에서는 남지 않는다. 채택 모델이 gpl3이므로 → BM25는 OFF 유지다.
그리고 이 사실이 §5의 채택 근거 3번이 됐다 — gpl3 단독 +0.0437 ≈ e5+BM25 +0.0430. 같은 크기의 이득인데 한쪽은 인덱스가 하나 더 필요하다.
12-4. v5 대조 — 하한 부호가 바뀌었다
M0 · C_doc → G_doc · MRR@10 | 값 | 판정 |
|---|---|---|
| v5/164문항 기준 | +0.0222 [−0.0184, +0.0642] | 미채택 (CI 0 걸침) |
| v7/251문항 | +0.0430 [+0.0032, +0.0869] | ✅ 채택 조건 충족 |
하한 부호 변경. 즉 —
v5의 BM25 미채택은 판정이 틀렸던 게 아니라 성립하지 않았던 것이다.
임베딩 축에서 벌어진 일과 같은 구조다(§7 · M2의 +0.0297 [−0.0042, +0.0654] → +0.0578 [+0.0210, +0.0972]). 12-1에서 “한쪽만 다시 재면 그것이 편향”이라고 적었던 것의 답이 이것이다 — 같은 잣대로 다시 쟀고, 같은 종류의 결과가 나왔다.
v5/164문항 기준 나머지 줄은 bge-m3 −0.0035 · KURE-v1 −0.0127로 점추정부터 음수였고, v7에서도 각각 +0.0016 · −0.0027로 0 근처에 머문다. 이쪽은 부호가 바뀌지 않았다.
12-5. 부수 지표 회귀 장치가 실제로 작동했다
§12-8의 전제(“판정 지표 하나만 보지 않는다”)를 채택 조건에 네 번째 항으로 넣어 뒀는데,
E_doc 3건에서 실제로 걸렸다.
| 걸린 조합 | MRR@10 | 유의 음수가 난 부수 지표 |
|---|---|---|
M1 E_doc | −0.0004 | nDCG@10 −0.0237 [−0.0426, −0.0045] |
M2 E_doc | −0.0007 | nDCG@10 −0.0250 · Recall@5 −0.0354 |
M3 E_doc | −0.0088 | nDCG@10 −0.0271 · Recall@5 −0.0365 |
다만 이 장치가 판정을 바꾸지는 않았다 세 건 다 MRR 조건도 이미 불충족이라, 부수 지표가 없었어도 미채택이었다. 장치가 결과를 바꿨다고 쓰면 과장이다. 확인된 것은 *“작동은 한다”*까지다.
E_doc이 걸린 것 자체는 설명이 된다 —E_doc은 후보 예산 20개 고정이라 BM25 청크가 dense 청크를 밀어낸다. 상위 5칸이 잠식되면 nDCG·Recall@5부터 무너진다. v5에서 BM25가 “MRR로는 유의차 없음인데 nDCG·Recall로는 유의하게 나빴던” 것과 같은 메커니즘이다.
12-6. 골든셋 v7 — 자를 바꿔도 12.7이 안 흔들렸다
이 재측정을 위한 라벨링으로 골든셋이 한 단계 올라갔다.
| v6 | v7 | |
|---|---|---|
| 문항 (300 / 라벨링 대상 254 / 채점 성립 251) | 동일 | 동일 — 불변 |
| 정답 청크 | 486 | 490 (+4) |
| 판정 풀 구성 | 4모델 | 4모델 + BM25(문서 스코프) |
v6 스냅샷은 불변이다(불변 스냅샷 규약). v7은 그 위에 델타를 얹은 별도 정본이다.
그리고 12.7 임베딩 판정을 v7 라벨로 다시 돌렸다 — 세 승격 전부 유지
비교쌍 v6 라벨 v7 라벨 M3 ↔ M0_exact +0.0437 +0.0437 M1 ↔ M0_fast +0.0511 +0.0511 M2 ↔ M0_fast +0.0578 +0.0578 MRR@10이 소수 4자리까지 동일하다. 신규 정답 4건이 전부 BM25 전용 청크라
C_doc후보에 애초에 들어오지 않기 때문이다.자를 바꿔도 판정이 안 흔들린다는 견고성 근거이며, §3의 구간 이질성과는 성격이 다른 종류의 방어다.
12-7. ⚠️ 철회 — “nDCG 부호 역전”은 없던 현상이었다 (오라벨)
2026-07-31 정정. 이 절은 원래 *“M0·G_doc의 nDCG가 v5 −0.0364에서 v7 +0.0459로
부호가 뒤집혔는데 원인이 안 갈린다”*는 미해결 사항이었다. 그 전제가 틀렸다.
−0.0364 [−0.0622, −0.0101]는 M0(e5)가 아니라 M2(KURE)의 값이다.
| 근거 | 확인 |
|---|---|
| v5 정본 서술 | 「검색 실험 결과 (v5 164문항)」 §2-1-2가 “KURE-v1 기준”이라고 명시한다 |
| 같은 표의 MRR | −0.0127인데, 이는 KURE의 v5 G_doc 값과 일치한다(M0는 +0.0222) |
즉 M0의 v5 부수 지표는 애초에 기록이 없어 대조 자체가 불가능하다. 비교할 값이 없었으므로 뒤집힐 것도 없었다.
KURE로 제대로 대조하면 오히려 일관된다 — v5 nDCG −0.0364(유의) → v7 −0.0232 [−0.0498, +0.0037](방향 유지, 유의성 상실). 표본이 늘며 구간이 0을 품게 됐을 뿐 부호는 그대로다.
무엇이 이 오류를 만들었나 사전등록 스크립트
scripts/verdict_bm25_v6.py가 이 상수를V5_SECONDARY_M0_G라는 이름으로 들고 있었다. 값은 KURE 것인데 이름이 M0였다. 판정 로직은 이 상수를 쓰지 않으므로(대조 표시 전용) §12의 채택 판정은 영향받지 않는다. 그러나 이름 하나 때문에 없는 이상 현상이 미해결 사항으로 기록되고 문서 세 곳에 퍼졌다.교훈은 6.8.8과 같다 — 라벨이 값을 설명한다고 믿으면 안 된다. 값의 출처를 정본에서 되짚어야 한다. 상수는
V5_SECONDARY_M2_G로 고쳤고 정정 사유를 코드 주석에 남겼다.
12-8. 재측정에서도 유지한 전제
bm25_doc(문서 스코프)로만 쟀다. 전역 BM25는 v5에서 무효 처리됐다 — BM25 후보 20개 중 평균 6.5개(32.6%)가 대상 문서 밖이라 정답일 수가 없는 청크였다. 그 스코프 변경이 이번 미판정 편향의 직접 원인이기도 하다(§12-2)- 판정 지표 하나만 보지 않았다. 채택 조건에 부수 지표 유의 음수 없음을 네 번째 항으로 넣었고,
실제로
E_doc3건에서 걸렸다(§12-5) - 후보를 실제로 넓히는 구현으로 쟀다. BM25 전용 청크의 본문을 안 가져오는 반쪽 구현은 후보 집합을 못 넓혀 아무것도 재지 못한다
- 판정 규격을 수치 보기 전에 커밋했다.
scripts/verdict_bm25_v6.py가 사전등록본이고, 커밋은4b4f155다
13. 리랭커 축 — R2 미채택 · R1 ON 유지
결론: 한국어 튜닝 리랭커 R2는 전 모델에서 R1보다 유의하게 나쁘다. 리랭커 ON/OFF는 채택 모델 gpl3 위에서 ON이 유의하게 이기므로, 최종 구성은 R1 ON 유지다.
13-1. 판정 규격
v7 251문항 · 정답 490건 · 문서 스코프 _doc · 문서 클러스터 95군집 · 1만 회 · seed 20260729.
판정 스크립트는 scripts/verdict_r2_v7.py다. 커밋 ccfa8f9에서 R1↔R2 규격을 고정했고,
dc44275에서 A_doc 축을 추가했다.
| 축 | 비교쌍 | 방향 |
|---|---|---|
| 리랭커 종류 | R2 ↔ R1 (C_doc 고정) | R2 − R1 |
| 리랭커 ON/OFF | C_doc ↔ A_doc | C − A, 챌린저 = 리랭커 ON |
13-2. 리랭커 종류 — R2는 전 모델 미채택
| 모델 | MRR@10 Δ | 95% CI | nDCG@10 Δ | Recall@5 Δ | 판정 |
|---|---|---|---|---|---|
| M1 bge-m3 | −0.0897 | [−0.1176, −0.0618] | −0.0694 | −0.0715 | ❌ 미채택 |
| M2 KURE-v1 | −0.0891 | [−0.1168, −0.0614] | −0.0704 | −0.0652 | ❌ 미채택 |
| M3 gpl3 | −0.0885 | [−0.1199, −0.0578] | −0.0662 | −0.0379 | ❌ 미채택 |
| M0 e5-small | −0.0611 | [−0.0877, −0.0359] | −0.0450 | −0.0165 | ❌ 미채택 |
전 모델에서 MRR@10 CI 상한까지 음수다. 따라서 R2가 R1보다 한국어 RFP 순위에 낫다는 가설은 반증됐다. R2는 nDCG@10과 Recall@5에서도 같은 방향으로 손해가 난다.
채택 모델 gpl3 위의 R2 비교는 이번이 최초 측정이다. 이전 판에는 gpl3가 없어 대조값 자체가 없었는데, 처음 잰 줄에서도 R2는 R1에 유의하게 졌다.
13-3. 리랭커 ON/OFF — 임베딩이 강해질수록 이득이 줄어든다 ★
| 모델 | MRR@10 Δ (C−A) | 95% CI | 판정 |
|---|---|---|---|
| M0 e5-small | +0.1169 | [+0.0695, +0.1633] | ✅ 채택 |
| M3 gpl3 (채택 모델) | +0.0651 | [+0.0203, +0.1108] | ✅ 채택 |
| M2 KURE-v1 | +0.0301 | [−0.0153, +0.0781] | ❌ |
| M1 bge-m3 | +0.0195 | [−0.0216, +0.0611] | ❌ |
임베딩이 강해질수록 리랭커의 이득이 줄어든다. e5에서 +0.1169이던 것이 gpl3에서 +0.0651로 절반이 되고, bge·KURE에서는 유의성을 잃는다(CI가 0을 걸침). 리랭커가 대신하던 일을 임베딩이 직접 하게 되는 것이다.
이 발견은 채택 결정 자체를 바꾸지 않는다. gpl3는 리랭커 ON이 유의하게 이기므로 R1 ON 유지다. 다만 bge·KURE를 골랐다면 리랭커를 끄는 것이 선택지가 될 수 있었다. 리랭커는 질의마다 비용이 들기 때문에, 그 경우에는 ON/OFF를 비용 조건과 함께 다시 판단해야 한다.
13-4. 안 재는 변형은 그대로 안 잰다
| 변형 | 판정 | 근거 |
|---|---|---|
B_doc (dense+BM25, 리랭커 OFF) | 안 잼 | 프로덕션 구성이 아니다 |
D_doc (전역 BM25 + 리랭커) | 안 잼 | 전역 BM25는 후보 32.6%가 대상 문서 밖이다 |
D_doc(전역 BM25 + 리랭커) · 근거: 전역 BM25는 후보의 32.6%가 대상 문서 밖이라 정답일 수가 없다. 문서 스코프G_doc이 대체한다
즉 이번 추가 측정은 프로덕션 후보가 되는 리랭커 축만 닫은 것이다.
14. 산출물
| 산출물 | 역할 |
|---|---|
scripts/verdict_12_7.py | 임베딩 승격 판정 스크립트 |
out/verdict_12_7.json | 임베딩 승격 판정 결과 |
out/rebuttal_12_7.md | v5/164문항 판정 불가에 대한 재검토 메모 |
out/rescore_v6/rescore_v6_{M0_exact,M0_fast,M1,M2,M3}.json | 모델별 재채점 순위 덤프 5벌 |
out/rescore_v6/rescore_v6_redacted.json | 위의 NDA 안전 요약본 (aggregate + 소요 시간) |
data/rerank_labels_v6.jsonl | 병합된 v6 정답 라벨 (집합 연산, 251문항 채점 성립) |
VM /srv/GPTPilot/goldenset/v6_20260731/ | 골든셋 v6 스냅샷 · r--r--r-- (불변 스냅샷 규약) |
| BM25 축 (§12) | |
scripts/verdict_bm25_v6.py | BM25 축 판정 스크립트 |
out/verdict_bm25_v7.json | BM25 축 판정 결과 |
out/verdict_12_7_v7.json | 12.7 임베딩 판정의 v7 라벨 재확인 — 세 승격 유지(MRR 4자리 동일) |
out/bm25_unjudged_audit_v6.json | BM25 미판정 편향 감사 결과 |
data/goldenset_v7/rerank_labels_full.jsonl | v7 정답 라벨 (정답 490건, 문항 수 불변) |
out/rescore_v6_bm25/ | BM25 변형 리랭킹 순위 덤프 20벌 |
VM /srv/GPTPilot/goldenset/v7_20260731/ | 골든셋 v7 스냅샷 · current 이동 완료 |
| 리랭커 축 (§13) | |
scripts/verdict_r2_v7.py | 리랭커 R2/R1 판정 스크립트 |
out/verdict_r2_v7.json | 리랭커 판정 정본 — R2 전 모델 미채택 · R1 ON은 M0·M3에서 채택 |
| ADR-010 | 신설 완료 — gpl3 채택 |
| ADR-011 | 신설 완료 — BM25 OFF 유지, 단 근거 정정 |
scripts/verdict_12_7.py· 역할: 사전등록된 판정 규격 — 승격 기준·부트스트랩·클러스터·6.8.9 대조 상수. 구조 회귀 테스트 포함out/verdict_12_7.json· 역할: 판정 정본 — 델타 · 95% CI · 유형별 평균차 · 승격 판정 ·재검토_필요플래그out/rebuttal_12_7.md· 역할: 반박 대비 논점 — 구간별 재판정 · 이득 분해 · 재표집 단위 비교 · 추가 측정 목록scripts/verdict_bm25_v6.py· 역할: 사전등록된 BM25 판정 규격 — 채택 4조건(부수 지표 항 포함)·v5 대조 상수. 수치 보기 전에 커밋(4b4f155)out/verdict_bm25_v7.json· 역할: BM25 판정 정본 — 4모델 ×E/F/G_doc15비교 · 델타 · CI · 유형별 · 부수지표 회귀 플래그out/bm25_unjudged_audit_v6.json· 역할: 미판정 편향 감사 — BM25 전용 2,819건 / 미판정 734건(26.0%) / 118문항scripts/verdict_r2_v7.py· 역할: 사전등록된 리랭커 판정 규격 — R1↔R2는 커밋ccfa8f9,A_doc축은dc44275에서 추가
verdict_12_7.py는 초기 실행에서 KeyError: 'C_doc'을 냈고 3772022에서 수정됐다.
같은 커밋에서 산출물 구조 회귀 테스트를 추가했다 — 판정 스크립트가 조용히 다른 구조를 내면
그다음 해석이 전부 어긋나기 때문이다. 현재 스크립트는 §2의 세 값을 소수점 4자리까지 그대로 재현한다.
15. 관련 문서
| 문서 | 무엇을 보러 |
|---|---|
| 검색 실험 설계 (4모델·BM25) | 측정 조건 정본 |
| 골든셋 · 골든셋 200문항 제작 | 정답지가 어떻게 만들어졌나 |
| 쿼리 파이프라인 | 검색이 전체 흐름에서 어디인가 |
| paired bootstrap · 상·하한 채점 · 후보 캐시 · 지표 포화 | 이 실험이 쓴 방법 |
| 개념 색인 | MRR · nDCG · BM25 · 크로스인코더 리랭커 등 |
_archive/검색 실험 결과 (v5 164문항).md | v5/164문항 기준의 이전 판정 |
- 검색 실험 설계 (4모델·BM25) · 무엇을 보러: 측정 조건 정본 — 파이프라인 · 정답지 · 지표 · 통제 · 게이트
_archive/검색 실험 결과 (v5 164문항).md· 무엇을 보러: 이전 판 결과 — 절대값을 이 문서와 나란히 놓지 마라
코드: scripts/verdict_12_7.py · scripts/verdict_bm25_v6.py · scripts/verdict_r2_v7.py
코드: scripts/build_pool_v6.py · scripts/build_pool_delta.py · src/eval/label_bias.py
코드: src/eval/run_eval.py · src/query/retrieve.py
정본: Plans.md Phase 12 (12.2a~12.8) · spec.md §2 · §10-19 · docs/ADR.md ADR-010 · ADR-011