Embedding 모델 절제실험(ablation)

issue #148 추적. README 의 “Embedding 모델 ablation 미실행” 단서를 측정된 첫 비교 + 재현 가능한 러너(runner)로 갱신한다.

범위(Scope)

(프로젝트 시작 이래) 기본 임베딩(embedding)은 2019 년 sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 이다. README 는 오랫동안 이것을 현대 다국어(multilingual) 모델과 비교해야 한다고 표시해 왔다. 이 페이지는 그 비교의 첫 결과 + 이를 확장하는 경로다.

러너(Runner)

# Default: compare MiniLM-L12-v2 vs multilingual-e5-base
python3 scripts/run_embedding_ablation.py

# Add more models — careful with disk (BGE-M3 ~2GB, e5-large ~1.3GB)
python3 scripts/run_embedding_ablation.py \
    --models \
        sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 \
        intfloat/multilingual-e5-base \
        intfloat/multilingual-e5-large \
        BAAI/bge-m3

# Reuse already-computed summaries (skip build_index/run_eval if cached)
python3 scripts/run_embedding_ablation.py --reuse-existing

러너는 모델별 산출물(artifact)을 data/embedding-ablation/<model_slug>/ (index) 와 reports/embedding-ablation/<model_slug>/eval_summary.json 아래에 저장한다. 둘 다 gitignore 처리된다 (outputs/* + reports/* 규칙에 따라).

첫 번째 비교 — MiniLM-L12-v2 vs multilingual-e5-base

실행일: 2026-05-11. Public fixture smoke corpus (n=42; single_doc 14 / comparison 10 / follow_up 9 / abstention 9).

헤드라인 수치 (full 파이프라인)

ablation metric MiniLM-L12-v2 multilingual-e5-base Δ (pp)
full accuracy 0.906 0.906 +0.0
full groundedness 0.929 0.929 +0.0
full citation_precision 0.905 0.905 +0.0
full abstention 1.000 1.000 +0.0
full format compliance 0.905 0.905 +0.0

임베딩이 실제로 차이를 만드는 지점

ablation metric MiniLM-L12-v2 multilingual-e5-base Δ (pp)
naive_baseline accuracy 0.656 0.844 +18.8
naive_baseline groundedness 0.595 0.714 +11.9
naive_baseline citation_precision 0.488 0.548 +6.0
naive_baseline format compliance 0.548 0.667 +11.9

다른 모든 agentic 절제실험(hierarchical, no_metadata_first, no_rerank, no_verifier_retry)은 주요 지표에서 0pp 델타를 보인다.

Chunk 단위 검색(retrieval) (사람이 주석한 gold 부분집합, n=10)

Issue #175 가 8 개의 follow_up + 2 개의 single_doc chunk-boundary 케이스에 명시적 gold_chunk_ids 를 추가했다. 주석된 부분집합에 대한 slice 별 평균 (재실행 2026-05-11, naive_baseline, hashing backend):

slice n_annotated chunk_recall@5 chunk_MRR chunk_nDCG@10
single_doc (chunk-boundary probes) 2 1.000 0.750 0.815
follow_up 8 0.750 0.750 0.750

주석 결과: 휴리스틱(heuristic)으로 도출한 gold 와 사람이 주석한 gold 가 10 개 케이스 모두에서 일치한다 — 0.750 의 follow_up 점수는 retriever 가 chunk 를 전혀 반환하지 않는 두 개의 multi-turn 케이스(follow_up_state_a_security, follow_up_state_multi_step_a_deliverables, issue #57 C4 로 추적)를 반영하며, gold 라벨링 artifact 가 아니다. 이제 임베딩 모델 비교는 이 케이스들에서 검색 누락(retrieval miss)을 휴리스틱 사각지대(blind spot)와 구분할 수 있다.

결과 읽기

  1. full agentic 파이프라인에서 임베딩 선택은 이 corpus 에서 무관하다. Metadata-first 필터링(ADR 0002)이 대부분의 쿼리에 대해 dense 검색을 우회하므로, 더 나은 임베딩은 도움이 되지 않는다. 이는 metadata-first 설계의 경험적(empirical) 검증이다 — 파이프라인이 suboptimal 임베딩에 강건(robust)하다.
  2. naive (dense-only) 검색에서 임베딩 선택은 크게 중요하다. multilingual-e5-base 가 accuracy 를 0.656 에서 0.844 로 (+18.8pp) 끌어올린다. 그 대부분은 dense retriever 가 MiniLM 이 놓쳤던 기대 문서를 마침내 찾아낸 데서 온다.
  3. 기본값 변경 없음. CI 경로는 hashing 을 유지하고 (ADR 0001 재현성에 따라) README 기본값은 MiniLM-L12-v2 를 유지하는데, full 파이프라인 지표가 동일하기 때문이다. 더 영향력 있는 corpus 가 다른 결과를 보이면 향후 PR 이 재검토할 수 있다.
  4. Reviewer 화제(talking point). “2026 년에 왜 MiniLM 인가?” 라고 묻는 reviewer 는 측정된 답을 얻는다: “metadata-first 필터링이 agentic 파이프라인을 임베딩 선택에 강건하게 만든다; multilingual-e5-base 로 naive baseline 에서 +18.8pp accuracy lift 를 측정했지만 full 파이프라인에서는 0pp 였다.”

두 번째 비교 — Phase 1.2 (issue #174): 부분 3-of-4 측정

이 사이클은 OpenAI Embeddings API 를 일급 backend 로 추가하고 model ID 로부터 backend 를 자동 도출한다 (text-embedding-* → openai, 그 외 sentence-transformers). 러너는 이제 현대 다국어 SoTA(BGE-M3, e5-large-instruct), 한국어 특화(KoSimCSE), 그리고 유료 외부 baseline(OpenAI text-embedding-3-large)을 아우른다.

Issue #174 (이 섹션)는 ADR 0019 에서 명명된 후보들을 실행했다. 넷 중 셋은 완료까지 실행됐다; BAAI/bge-m3 는 ADR 0019 condition 1 의 torch 절반에 막혀 있다.

재현(Reproduction)

# Phase 1.2 measured set (~1.8GB disk, ~5 min cold cache on this corpus)
python3 scripts/run_embedding_ablation.py --models \
    sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 \
    intfloat/multilingual-e5-large-instruct \
    BM-K/KoSimCSE-roberta-multitask

# BGE-M3 — still blocked, requires torch >= 2.6 (see env section below)
python3 scripts/run_embedding_ablation.py --models BAAI/bge-m3

# OpenAI text-embedding-3-large (3072-dim) — ~$0.004 for n=42 corpus
export BIDMATE_OPENAI_API_KEY=sk-...
python3 scripts/run_embedding_ablation.py --models text-embedding-3-large

모델별 산출물은 data/embedding-ablation/<slug>/ (index) 와 reports/embedding-ablation/<slug>/eval_summary.json (eval) 으로 간다. 둘 다 gitignore 처리된다.

대략적인 디스크 + 비용 가이드

model disk dim cost notes
BAAI/bge-m3 ~2.0GB 1024 free 2024 multilingual SoTA — env-blocked (torch < 2.6)
intfloat/multilingual-e5-large-instruct ~1.3GB 1024 free instruction-tuned, measured this cycle
BM-K/KoSimCSE-roberta-multitask ~0.5GB 768 free Korean-specialized, MEAN-pooling fallback (model is not packaged for sentence-transformers; the runner wraps it with default mean-token pooling)
nlpai-lab/KURE-v1 ~1.1GB 768 free Korean-specialized — Phase 1.3 candidate (deferred)
text-embedding-3-large n/a 3072 ~$0.13 / 1M tokens (~$0.004 / n=42) OpenAI — Phase 1.3 candidate

이 사이클의 env 상태

Phase 1.2 는 원래 ADR 0019 분석의 두 env blocker 중 하나를 해소했다; 다른 하나는 남아 있다:

dependency observed required status
huggingface-hub 0.36.2 < 1.0 ✅ cleared — intfloat/multilingual-e5-large-instruct loaded cleanly
torch 2.2.2 >= 2.6 ❌ still blocking BAAI/bge-m3 (CVE-2025-32434 hard requirement in sentence_transformers load path)

requirements.txt 에 torch >= 2.6 을 고정하는 향후 PR 이 BGE-M3 를 풀어주고 Phase 1.3 를 trigger 한다 (재실행 한 번 더; 러너는 --reuse-existing 을 통해 idempotent 하다).

헤드라인 수치 — Phase 1.2 (측정 2026-05-12, n=42)

Public fixture smoke corpus (첫 비교와 동일한 n=42 split). 95% bootstrap CI 는 괄호 안에.

full agentic 파이프라인 — ADR 0019 condition 3 이 설정한 기준선

metric MiniLM-L12-v2 e5-large-instruct KoSimCSE-roberta-multitask Δ vs MiniLM (e5) Δ vs MiniLM (KoSimCSE)
accuracy 0.906 [0.781, 1.000] 0.906 [0.781, 1.000] 0.906 [0.781, 1.000] +0.0 +0.0
groundedness 0.929 [0.857, 1.000] 0.929 [0.857, 1.000] 0.929 [0.857, 1.000] +0.0 +0.0
citation_precision 0.905 [0.821, 0.976] 0.905 [0.821, 0.976] 0.905 [0.821, 0.976] +0.0 +0.0
abstention 1.000 [1.000, 1.000] 1.000 [1.000, 1.000] 1.000 [1.000, 1.000] +0.0 +0.0
format compliance 0.905 [0.810, 0.976] 0.905 [0.810, 0.976] 0.905 [0.810, 0.976] +0.0 +0.0

세 모델은 full 에서 bit-identical 한 지표 값을 산출한다 — 단지 CI 가 겹치는 정도가 아니다. 동일한 CI 는 자명하게 따라온다 (전반적으로 +0.0 델타).

naive_baseline (ADR 0001 에 따라 ablation 으로 보존 — ADR 0019 condition 3 에는 미산입)

metric MiniLM-L12-v2 e5-large-instruct KoSimCSE-roberta-multitask Δ vs MiniLM (e5) Δ vs MiniLM (KoSimCSE)
accuracy 0.656 [0.500, 0.812] 0.844 [0.719, 0.969] 0.781 [0.625, 0.906] +18.8 +12.5
groundedness 0.595 [0.452, 0.738] 0.714 [0.571, 0.833] 0.667 [0.524, 0.786] +11.9 +7.1
citation_precision 0.488 [0.357, 0.619] 0.560 [0.440, 0.679] 0.488 [0.369, 0.607] +7.1 +0.0
abstention 0.300 [0.000, 0.600] 0.300 [0.000, 0.600] 0.300 [0.000, 0.600] +0.0 +0.0
format compliance 0.548 [0.405, 0.690] 0.667 [0.524, 0.810] 0.619 [0.476, 0.762] +11.9 +7.1

첫 사이클 발견과 동일한 형태다 — 현대 다국어 모델과 한국어 특화 모델 모두 dense-only 검색을 실질적으로 개선하지만, production 파이프라인(full)은 대부분의 쿼리에 대해 dense 를 우회하므로 어느 lift 도 전이되지 않는다.

Phase 1.2 부분 결과 읽기

  1. ADR 0019 condition 3 은 trigger 되지 않는다. 측정된 두 후보 모두 full.accuracy 와 full.groundedness 에서 0pp 델타를 보인다. 점 추정치(point estimate)가 동일하면 CI 질문은 무의미하다. 기본값은 MiniLM-L12-v2 를 유지한다.
  2. 0pp-on-full 패턴은 방금 확장한 임베딩-품질 축 전반에서 강건하다. 첫 사이클은 e5-base (구형 다국어)에서 이를 보였다. Phase 1.2 는 e5-large-instruct (2024 SoTA, instruction-tuned, 1024-dim) 와 KoSimCSE-roberta-multitask (한국어 특화)에서 이를 확인한다. “현대 / 한국어 모델이 패턴을 깰지 모른다” 는 가설은 이 corpus 에서 반증됐다.
  3. ADR 0002 (metadata-first 검색)에 대한 경험적 지지. Metadata-first 는 임베딩 선택이 중요해질 기회를 갖기 전에 대부분의 쿼리를 dense 검색에서 멀리 라우팅한다. full 파이프라인이 7 년 묵은 임베딩에 강건한 것은 운이 아니다 — metadata-first 설계가 임베딩-품질 축을 흡수하는 것이다.
  4. naive_baseline 은 임베딩에 따라 계속 움직인다. e5-large-instruct 는 naive_baseline.accuracy 를 0.656 → 0.844 로 (+18.8pp, e5-base 의 첫 사이클 델타와 일치) 끌어올린다. KoSimCSE 는 +12.5pp 를 더한다. ADR 0001 이 naive 를 ablation 표면으로 보존하므로 이 델타들은 관측 가능하되 기본값에 대해 actionable 하지는 않다.
  5. BGE-M3 가 유일한 named-candidate 갭이다. ADR 0019 condition 2 (“완료까지 실행”)는 이 사이클에서 부분적으로 충족됐다. 남은 작업은 torch >= 2.6 requirements.txt bump — 측정 결정이 아닌 집중된 chore PR 이다. 그것이 머지되면 Phase 1.3 가 BGE-M3 를 대상으로 재실행한다.

이 보류 자체가 ADR 가치가 있는 이유

기본값이 바뀌지 않았으므로 경험적 결정에는 여전히 ADR 이 없다 — 하지만 보류 자체가 이제 load-bearing 하다. ADR 0019 가 없으면 다음 기여자는 (a) 동일하게 막힌 측정을 재실행하거나, (b) 경험적 기준 없이 조용히 기본값을 교체할 것이다. ADR 0019 는 “MiniLM 유지” 결정과 그것이 재개되는 명시적 조건 둘 다를 못박는다.

향후 ablation 이 (naive_baseline 만이 아니라) full 을 유의미하게 개선하는 모델을 찾고 팀이 기본값 전환을 결정한다면, 그 변경은 CLAUDE.md “ADR threshold” 에 따라 follow-up ADR 과 함께 머지돼야 한다. OpenAI backend 추가는 stub-default 하의 additive ablation 표면이다 (CI 는 EMBEDDING_BACKEND=hashing 으로 실행하고 OpenAI 를 결코 호출하지 않는다) — ADR 0011 과 동일한 패턴.

세 번째 비교 — Phase 1.3 (issue #389): BGE-M3 가 ADR 0019 condition 2 를 닫음

Phase 1.2 는 BAAI/bge-m3 를 유일한 named-candidate 갭으로 남겼는데, maintainer 의 로컬 Python 설치가 torch 2.2.2 였기 때문이다 — 현대 sentence_transformers 가 BGE-M3 의 커스텀 loader 코드에 대해 강제하는 torch >= 2.6 CVE-2025-32434 완화책에 못 미친다. requirements.txt 가 torch >= 2.6 을 고정하자 (ADR 0019 가 표시한 chore PR), Phase 1.3 는 “새 venv 를 만들고, BGE-M3 단독으로 러너를 실행하고, 행을 추가” 로 축소됐다.

이 사이클의 env 상태

원래 ADR 0019 분석의 두 blocker 가 이제 모두 해소됐다:

dependency observed (Phase 1.3 venv) required status
torch 2.11.0 >= 2.6 ✅ cleared — requirements.txt:8 pin, BAAI/bge-m3 loads cleanly
huggingface-hub 0.36.2 < 1.0 ✅ cleared (since Phase 1.2)

헤드라인 수치 — Phase 1.3 (측정 2026-05-12, n=42)

Phase 1.1 / 1.2 와 동일한 n=42 public fixture smoke corpus.

full agentic 파이프라인 — ADR 0019 condition 3 평가자

metric MiniLM-L12-v2 BGE-M3 Δ vs MiniLM
accuracy 0.906 0.906 +0.0
groundedness 0.929 0.929 +0.0
citation_precision 0.905 0.905 +0.0
abstention 1.000 1.000 +0.0
format compliance 0.905 0.905 +0.0

넷 중 넷. BGE-M3 는 bit-identical 한 full 지표를 산출한다 — 단지 CI 가 겹치는 정도가 아니다 — Phase 1.2 의 e5-large-instruct 와 KoSimCSE-roberta-multitask 가 그랬듯이. 동일한 CI 가 따라온다.

naive_baseline (ADR 0001 에 따라 ablation 으로 보존 — ADR 0019 condition 3 에는 미산입)

metric MiniLM-L12-v2 BGE-M3 Δ vs MiniLM
accuracy 0.656 0.844 +18.8
groundedness 0.595 0.714 +11.9
citation_precision 0.488 0.548 +6.0
abstention 0.300 0.300 +0.0
format compliance 0.548 0.667 +11.9

BGE-M3 는 e5-large-instruct 와 동일한 naive_baseline 천장에 도달한다 (둘 다 accuracy 를 0.656 → 0.844, +18.8pp 끌어올림). dense-only retriever 는 올바른 문서를 찾는 데 월등히 낫다; agentic 파이프라인은 대부분의 쿼리에 대해 dense 를 우회하고 그 lift 를 흡수한다.

다른 절제실험 (no_metadata_first / no_rerank / hierarchical / no_verifier_retry)

넷 모두 모든 지표에서 MiniLM 대비 +0.0 델타를 보인다 — full 과 동일한 패턴. 러너 출력은 reports/embedding-ablation/BAAI_bge_m3/eval_summary.json 에 보존된다.

Phase 1.3 결과 읽기

  1. ADR 0019 condition 2 가 완전히 충족된다. ADR-0019 가 명명한 네 후보 (MiniLM, e5-large-instruct, KoSimCSE, BGE-M3) 모두가 이제 n=42 public synthetic corpus 를 대상으로 완료까지 실행됐다. 더 이상 “보류된” 측정은 없다.
  2. ADR 0019 condition 3 은 BGE-M3 에 대해서도 trigger 되지 않는다. 0pp-on-full 패턴은 네 후보 전반에서, 그리고 MiniLM (2019), e5-base (2023), e5-large-instruct (2024 SoTA), KoSimCSE (한국어 특화), BGE-M3 (2024 multi-functional) 전반에서 강건하다. “현대 모델이 패턴을 깬다” 와 “한국어 특화 모델이 패턴을 깬다” 가설은 이 corpus 에서 둘 다 반증됐다.
  3. 기본값은 MiniLM-L12-v2 를 유지한다. ADR 0019 는 accepted 를 유지한다; follow-up ADR 0021 은 closure 를 문서화하는 보충(supplement) 이지 supersede 가 아니다.
  4. 이제 경험적 주장은 공개할 만큼 강하다. 2019–2024 에 걸친 다섯 임베딩, multilingual / instruction-tuned / 한국어 특화 / multi-functional: agentic 파이프라인의 full 지표는 움직이지 않는다. Metadata-first 검색 (ADR 0002)이 load-bearing 한 설계 선택이지, 임베딩 선택이 아니다.

네 번째 비교 — Phase 1.4 (issue #531, 2026-05-13): routed-subset saturation falsifier

ADR 0032이 제기한 질문: “0pp on full” 패턴이 metadata-first absorption의 artifact인가 (즉 임베딩 sensitivity를 측정 불가능하게 만드는가)?

측정 표면(Measurement surface)

eval/routed_config.yaml (n=11 케이스, PR #530 추가), agentic_full_routed preset (metadata_first: false). 측정 케이스는 metadata-first routing이 우회되도록 설계됨:

  • Multi-turn follow-up (3 cases): entity switch, implicit metric, 2-step implicit
  • Multi-doc comparison ambiguity (4 cases): 동일 metadata 후보가 ≥ 2 문서에 분포
  • Inference queries (3 cases): metadata column hook 없는 추론 질의
  • Abstention (1 case): corpus에 없는 정보에 대한 abstain 케이스

Runner: scripts/run_routed_measurement.py --backend sentence-transformers. 결과: reports/embedding_routed.json.

헤드라인 수치 — Phase 1.4 (측정 2026-05-13, n=11, routed surface)

Model full (metadata_first=true) accuracy routed (metadata_first=false) accuracy Notes
MiniLM-L12-v2 0.500 0.400 ADR 0019 default
multilingual-e5-large-instruct 0.500 0.400 ADR 0021 Phase 1.3
KoSimCSE-roberta-multitask 0.500 0.400 ADR 0021 Phase 1.2
BGE-M3 — — Skipped: torch ≥ 2.6 required (ADR 0021 §4 blocker)
KURE-v1 0.500 0.400 Korean-specialized; locally cached

Spread (top-vs-bottom, routed): 0.0pp (threshold: +3pp per ADR 0032 §Decision)

Phase 1.4 결과 읽기

  1. Saturation cross-validated: 0pp 패턴이 routed surface (metadata-first disabled)에서도 성립. Saturation 가설은 “metadata-first absorption만의 artifact”가 아님을 확인.
  2. 두 가지 상보 해석:
    • Corpus 규모 효과: fixture corpus (7 docs, 9 chunks)에서 dense retrieval은 어떤 임베딩으로도 9개 chunk 중 올바른 것을 회수 → 큰 corpus에서는 spread 발생 가능
    • Verifier 병목: accuracy를 제한하는 것이 retrieval 품질이 아니라 verifier exact-term match 정책 (ADR 0004 설계 의도)
  3. ADR 0019 lock은 measurement-precluded가 아닌 empirically justified: 두 surface(full + routed) 모두에서 0pp. Re-open condition 3 (≥ +5pp non-overlapping CIs)은 evidence-backed stable.
  4. ADR 0032 accepted로 closes: 측정 surface 자체가 목표였으며, spread < +3pp 결과로 ADR 0032 자동 close. ADR 0019 default lock 유지.

Fifth comparison — Phase 1.5 (issue #447, 2026-05-14): KURE-v1 Korean-specialized

ADR 0037이 제기한 질문: issue #447이 re-open 조건으로 명시한 nlpai-lab/KURE-v1을 n=100 full corpus 대상으로 실행하면 condition 3 (≥+5pp full lift)이 trigger되는가?

Corpus note: eval/config.yaml이 issue #570으로 n=42 → n=100으로 확장됐다. Phase 1.1–1.3의 ADR 0021 수치(accuracy 0.906 등)는 n=42 기준이므로 Phase 1.5 수치와 직접 비교 불가. 본 Phase는 같은 n=100 corpus 위에서 KURE-v1 vs MiniLM을 비교한다.

재현(Reproduction)

/opt/homebrew/opt/python@3.11/bin/python3.11 scripts/run_embedding_ablation.py \
    --models sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 \
             nlpai-lab/KURE-v1

환경: torch 2.6.0, sentence_transformers 2.7.0, torchvision 0.21.0.

헤드라인 수치 — Phase 1.5 (n=100, KURE-v1 vs MiniLM)

full agentic 파이프라인 (ADR 0019 condition 3 의 구속 게이트):

metric MiniLM KURE-v1 Δ (pp)
accuracy 0.731 0.718 −1.3
groundedness 0.750 0.750 +0.0
citation_precision 0.715 0.700 −1.5
abstention 0.818 0.818 +0.0
format compliance 0.620 0.620 +0.0

naive_baseline (ADR 0001 보존 ablation — 미산입):

metric MiniLM KURE-v1 Δ (pp)
accuracy 0.590 0.782 +19.2
groundedness 0.550 0.690 +14.0
citation_precision 0.440 0.530 +9.0
format compliance 0.520 0.640 +12.0

Phase 1.5 결과 읽기

  1. Condition 3 NOT triggered: full pipeline에서 KURE-v1은 MiniLM 대비 accuracy −1.3pp, groundedness +0.0pp. +5pp 임계값에 도달하지 못할 뿐 아니라 순 음수(-). 0pp-on-full 패턴이 여섯 번째 임베딩 피벗에서도 성립.
  2. Korean-specialization은 naive_baseline에서만 유효: +19.2pp accuracy lift는 인상적이지만 metadata-first routing (ADR 0002)이 agentic pipeline에서 dense retrieval을 우회하므로 full에 반영되지 않는다.
  3. Issue #447 closed: 세 가지 re-open 조건 모두 처리됨 — 조건 1 (스크립트 추가, docstring 이미 존재), 조건 2 (n=100 실행 완료), 조건 3 (NOT triggered). 결과는 MiniLM 기본값 유지를 지지한다.
  4. ADR 0019 default lock은 이제 6-pivot empirical basis: 2019–2024, multilingual / SoTA / Korean-specialized / multi-functional / Korean-specialized-v2 범주를 모두 커버했으며, 어느 것도 full 파이프라인 메트릭을 움직이지 못했다.

Phase 2.0 — real100 retrieval-surface (issue #1359, 2026-05-23): 5-model Korean embedding ablation

Current-use boundary. Phase 2.0의 real100 / reports/real100/* references are historical v1 aggregate-only snapshots. They are not current private-eval claim evidence; new task, PR, and handoff claims must use real100_v2 aggregate evidence plus make real-eval-v2-check, make real-eval-v2-inventory, and make real-eval-v2-guard.

Phase 1.x는 전부 public-fixture-smoke corpus + end-to-end answer-quality(accuracy/groundedness) 측정이었다. 두 가지 구조적 한계가 누적됐다:

  1. 합성 corpus saturation — Phase 1.4 falsifier(ADR 0032)가 routed subset에서 측정 천장을 입증.
  2. answer-surface가 임베딩 차이를 가린다 — Phase 1.5에서 KURE-v1이 naive_baseline(dense)에서 accuracy +19.2pp였지만 full에서 −1.3pp. metadata-first routing(ADR 0002) + hybrid(ADR 0058)가 dense 채널을 우회하기 때문. 즉 answer accuracy로는 임베딩 품질 자체를 분리 측정할 수 없다.

Phase 2.0은 두 축을 모두 바꾼다: (a) corpus를 real100(비공개 26k kordoc 청크, harder)로, (b) 측정 표면을 ADR 0069가 노출한 run-level retrieval aggregate(chunk_recall@{5,10,20} / chunk_mrr / chunk_ndcg@10 + bootstrap CI)로. 이렇게 하면 dense 검색이 실제로 끌어올리는 청크를 직접 측정 — answer 단계가 가리지 못한다. 이 작업이 ADR 0069 surface의 첫 소비자다.

측정 표면 — 모델 간 full(hybrid) recall 변동이 곧 임베딩 효과: real100 ablation 4종은 full(hybrid = dense 채널 + BM25 RRF 융합, ADR 0058) / random_retrieval(backend=random, floor) / single_chunk(backend=dense·단일청크 파이프라인, degenerate) / full_bm25s(역시 hybrid이며 bm25_backend만 bm25s로 스왑 — ADR 0057 라이브러리 비교 행)이다. 주의: full_bm25s는 순수-BM25 임베딩-독립 control이 아니다 — full과 동일한 hybrid 파이프라인에서 BM25 구현체만 바뀌므로 dense 채널(임베딩)에 똑같이 민감하다.

임베딩 효과는 모델 간 비교 그 자체로 분리된다: 청크 텍스트·청크 ID·BM25 입력이 모든 빌드에서 바이트-동일(5모델 인덱스 모두 26376 청크, MiniLM↔KURE 26376/26376 동일-위치-동일-텍스트 검증)하므로, 빌드 사이에 바뀌는 유일한 변수는 dense 임베딩이다. 따라서 full(또는 full_bm25s) recall의 모델 간 차이는 전적으로 dense 채널 = 임베딩 품질에 귀속된다. condition-3(ADR 0019)는 candidate의 full recall@10이 baseline 대비 ≥+5pp and non-overlapping CI일 때 default re-eval을 trigger한다.

Gold 주석 안정성: gold chunk는 derive_gold_chunk_ids(eval/scorers/chunk_metrics.py)가 인덱스에서 동적으로 도출한다 (doc_id ∈ expected_doc_ids AND text가 expected_term 포함). 동결된 gold_chunk_ids가 없으므로 csv_text→kordoc 청킹 변화에도 재주석 없이 유효하다.

모델 5종 + env

모델 HF id params dtype
MiniLM (baseline) sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 118M fp32
EmbeddingGemma-300M google/embeddinggemma-300m 300M fp32 (bf16-native)
bge-m3-korean upskyy/bge-m3-korean 568M fp16
KURE-v1 nlpai-lab/KURE-v1 568M fp16
Qwen3-Embedding-0.6B Qwen/Qwen3-Embedding-0.6B 600M fp16

env: sentence-transformers ≥5.0 (PR #1358 핀 bump — EmbeddingGemma/Qwen3 요구), torch 2.6, MPS. 메모리: real100 OOM(ADR 0058)은 m3 multi-vector였고 본 작업은 single-vector dense. 568M+ 모델은 fp32에서 16GB MPS OOM-kill(-9) → BIDMATE_ST_FP16=1로 적합 (XLM-R-large class). Gemma3는 bf16-native라 fp16 overflow 회피 위해 fp32. EmbeddingGemma는 gated → HF 토큰 필요. Qwen3는 eval의 query 인코딩에서 MPS+fp16+GQA 비호환으로 크래시 → 빌드(MPS)는 정상, eval만 CPU로 우회(아래 결과 읽기 §4).

재현(Reproduction)

# kordoc native HWP 파싱 강제(csv_text fallback 금지) + MPS + batch=8 + progress
export BIDMATE_REQUIRE_KORDOC=1 BIDMATE_TORCH_DEVICE=mps BIDMATE_ST_BATCH_SIZE=8 BIDMATE_ST_PROGRESS=1
export HF_TOKEN=...   # gated EmbeddingGemma

# fp32 그룹 (Gemma3 bf16-native)
python3 scripts/run_embedding_ablation.py --reuse-existing \
    --metadata-csv data/data_list.csv --files-dir data/files \
    --eval-config eval/real_config.local.yaml \
    --models sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 \
             google/embeddinggemma-300m

# fp16 그룹 (568M+ XLM-R-large class) — BIDMATE_ST_FP16=1 추가
BIDMATE_ST_FP16=1 python3 scripts/run_embedding_ablation.py --reuse-existing \
    --metadata-csv data/data_list.csv --files-dir data/files \
    --eval-config eval/real_config.local.yaml \
    --models upskyy/bge-m3-korean nlpai-lab/KURE-v1 Qwen/Qwen3-Embedding-0.6B

# Qwen3는 위에서 빌드는 되지만 eval query-encode가 MPS GQA로 크래시.
# 빌드된 인덱스를 재사용해 eval만 CPU로 재실행:
BIDMATE_TORCH_DEVICE=cpu python3 eval/run_eval.py \
    --index_dir data/embedding-ablation_real/Qwen_Qwen3_Embedding_0_6B \
    --output_dir reports/embedding-ablation_real/Qwen_Qwen3_Embedding_0_6B \
    --config eval/real_config.local.yaml

real100 인덱스/raw_results는 로컬·uncommitted (ADR 0005 경계). commit에는 aggregate retrieval 델타(means + CI)만 — per-case 텍스트 미노출. 5종 모두 로컬 HF·무료·네트워크 egress 없음 → ADR 0061 opt-in/baseline-byte-identity/data-boundary 3조건 충족 (외부 전송 백엔드와 달리 페이로드 유출 0).

헤드라인 수치 — Phase 2.0 (real100, n=114 retrieval-evaluable cases)

full (hybrid — 임베딩-민감 표면, ADR 0069 retrieval aggregate). baseline = MiniLM:

metric MiniLM EmbeddingGemma bge-m3-korean KURE-v1 Qwen3-0.6B
chunk_recall@5 0.200 [0.143,0.259] 0.210 [0.157,0.264] 0.232 [0.171,0.293] 0.259 [0.201,0.323] 0.245 [0.183,0.311]
chunk_recall@10 0.235 [0.174,0.298] 0.262 [0.202,0.328] 0.255 [0.195,0.321] 0.298 [0.233,0.367] 0.265 [0.204,0.333]
chunk_recall@20 0.235 [0.174,0.298] 0.262 [0.202,0.328] 0.255 [0.195,0.321] 0.298 [0.233,0.367] 0.265 [0.204,0.333]
chunk_mrr 0.415 [0.332,0.499] 0.462 [0.381,0.540] 0.463 [0.377,0.544] 0.548 [0.472,0.630] 0.483 [0.402,0.569]
chunk_ndcg@10 0.239 [0.187,0.294] 0.278 [0.224,0.333] 0.280 [0.224,0.336] 0.332 [0.272,0.394] 0.294 [0.236,0.354]

Δ vs MiniLM (recall@10, pp): EmbeddingGemma +2.7 · bge-m3-korean +2.0 · KURE-v1 +6.3 · Qwen3-0.6B +3.0. 5모델 전부 MiniLM CI와 중첩(overlap) — 점추정은 모두 양(+)이나 통계적으로 분리되지 않음. (mrr 델타: KURE +13.3 · Qwen3 +6.8 · Gemma +4.7 · bge +4.8 / ndcg@10: KURE +9.3 · Qwen3 +5.5 · bge +4.1 · Gemma +3.8 — 역시 전부 overlap.)

full_bm25s (hybrid + bm25s 라이브러리, ADR 0057 — full과 동일하게 임베딩-민감) recall@10: MiniLM 0.226 · EmbeddingGemma 0.265 · bge-m3-korean 0.258 · KURE-v1 0.300 · Qwen3-0.6B 0.266. 모델별로 full과 ≤0.5pp 차 (BM25 라이브러리 스왑은 ranking 거의 불변 → ADR 0057 parity 재확인). 모델 간 변동 폭은 full과 동일 → 변동의 출처가 BM25 구현이 아니라 dense 채널임을 교차 확인.

floor controls (모든 모델 공통 — 인덱스 sanity): random_retrieval recall@10 = 0.009, single_chunk(backend=dense, 단일청크) = 0.031. full이 random 대비 ~26x → 인덱스·gold 도출 정상.

Phase 2.0 결과 읽기

  1. real100에서 임베딩이 hybrid recall을 움직인다 — Phase 1.x와 정반대: full recall@10이 MiniLM 0.235 대비 4모델 모두 양(+)으로 상승 — KURE-v1 +6.3pp(0.298, 최고), Qwen3-0.6B +3.0pp, EmbeddingGemma +2.7pp, bge-m3-korean +2.0pp. mrr·ndcg도 동일 순위(KURE가 mrr +13.3pp / ndcg +9.3pp로 압도적). Phase 1.5에서 KURE의 한국어 특화는 full answer accuracy를 −1.3pp로 전혀 못 움직였는데(routing이 dense 우회), retrieval 표면 + harder real corpus에서는 dense 채널이 실제로 더 많은 gold 청크를 끌어올린다. 즉 ADR 0069 retrieval surface가 answer surface가 가렸던 임베딩-품질 신호를 드러냈다 — 본 Phase의 핵심 발견. 한국어 특화 모델(KURE)이 범용 multilingual(MiniLM) 대비 우위, 모델 크기보다 한국어 도메인 정합이 신호.
  2. 그러나 condition-3 NOT triggered (CI 중첩): KURE +6.3pp는 +5pp 임계를 넘지만 CI [0.233,0.367]가 MiniLM [0.174,0.298]와 중첩한다. ADR 0019 condition-3은 ≥+5pp and non-overlapping CI 둘 다 요구하므로 default-flip은 trigger되지 않는다. n=114의 검정력 한계 — 점추정 신호는 강하나 통계적 분리는 미달. follow-up: n 확대 또는 paired bootstrap로 검정력 보강 시 KURE 재평가 가치(가장 유력한 default-flip 후보).
  3. full_bm25s 교차검증: full과 full_bm25s(BM25 lib만 스왑)가 모델별 ≤0.5pp로 일치하고, 모델 간 변동 폭은 양쪽 동일. BM25 입력(청크 텍스트)이 빌드 간 바이트-동일이므로 변동의 원천은 dense 임베딩 하나로 확정 — 측정 타당성 확인.
  4. Qwen3는 MPS-incompatible(eval 단계), CPU로 우회: Qwen3-0.6B 인덱스 빌드(26k 청크 인코딩)는 fp16/MPS로 성공했으나 eval의 query 인코딩에서 mps.matmul이 GQA(grouped-query attention, 16 query heads / 8 KV heads) head-broadcast의 result type 추론에 실패(LLVM ERROR: Failed to infer result type, tensor 1×16×25×128 vs 1×8×128×25) — OOM이 아닌 MPS+fp16+GQA 하드 비호환. 빌드 산출 인덱스를 재사용해 eval만 BIDMATE_TORCH_DEVICE=cpu로 재실행(query 인코딩만 CPU, passage 인덱스는 재사용)하여 우회. 다른 4모델(MiniLM/Gemma/bge/KURE)은 GQA 미사용이라 MPS fp16에서 정상.

함께 보기