Module 03 — Embedding & Vector DB¶
학습 목표
이 모듈을 마치면:
- Define Embedding, ANN, FAISS, BGE 의 정의를 구분할 수 있다.
- Explain Cosine similarity 가 왜 의미 검색의 표준이 되었는지 설명할 수 있다.
- Apply 사내 코드/문서를 chunk → embed → FAISS index 로 만드는 파이프라인을 설계할 수 있다.
- Analyze IVF · HNSW · PQ 의 메모리/지연/정확도 trade-off 를 데이터 규모별로 분석할 수 있다.
- Evaluate 검색 품질을 MRR / nDCG 로 평가하고, 임베딩 모델 후보를 선정할 수 있다.
사전 지식
1. Why care? — Context window 만으로는 안 되는 이유¶
1.1 시나리오 — 200K context 가 부족 한 이유¶
당신은 Claude Sonnet (200K context) 으로 사내 IP 스펙 + 코드 + 디자인 문서를 다뤄야 합니다. 첫 번째 본능 — "모든 걸 prompt 에 넣자".
세 가지가 동시에 깨집니다:
- 물리적 한계: 사내 RDMA-TB 만 해도 ~2M token. RTL 까지 합치면 10M+. 200K 에 안 들어감.
- 비용 폭주: 200K 입력 = 약 $0.6/호출 (Claude). 100 호출/day = $60/day = $1800/month. 다중 사용자면 10×.
- Lost in the middle: 같은 200K 라도 중간 의 정보는 모델이 잘 안 봄 [Liu et al. 2023]. 가장 좋은 컨텐츠를 끼워 넣어도 답이 깨질 수 있음.
임베딩 + 벡터 DB 가 외부 메모리 역할을 합니다. 필요한 chunk 만 골라서 prompt 에 넣으면:
| 항목 | All-in-context | Embedding + Retrieval |
|---|---|---|
| 입력 토큰 | 200K | 2K (top-10 chunks) |
| 비용/호출 | $0.6 | $0.006 |
| Lost-in-middle | 발생 | 회피 |
| 사내 docs 전체 처리 가능? | ✗ (10M+) | ✓ |
이 두 컴포넌트의 품질이 RAG / Agent 시스템의 상한선 입니다 — 검색이 망가지면 그 위에 어떤 LLM 을 얹어도 답이 깨집니다.
🤔 잠깐 — 왜 BM25 키워드 검색 으로 안 되나?
BM25 (TF-IDF 의 개선) 도 도서관 검색 표준. 왜 embedding 이 필요?
정답
의미적 매칭.
예: 사용자가 "TLB flush" 검색. - BM25: "TLB" + "flush" 단어 그대로 포함된 문서만 hit. "TLB invalidation" 라는 다른 표현은 miss. - Embedding: "TLB flush" 와 "TLB invalidation" 이 같은 의미 영역 에 살아서 둘 다 hit.
Embedding 의 단점도 있음: 정확한 키워드 매칭 이 BM25 보다 약함 (예: 함수 이름 검색).
그래서 프로덕션 RAG 는 보통 hybrid (BM25 + Embedding 의 score fusion). 단독 사용은 둘 다 부족.
2. Intuition — "도서관 의미 색인" 비유와 한 장 그림¶
💡 한 줄 비유
Embedding + Vector DB ≈ 도서관 의미 색인 — 키워드(BM25) 가 제목·저자 색인이라면, 임베딩은 주제·뉘앙스 색인.
"TLB flush" 로 검색해도 "TLB invalidation" 책이 함께 나오는 이유는 두 단어가 같은 의미 좌표 에 살기 때문.
한 장 그림 — Embedding + ANN 검색의 전체 구조¶
왜 이 구조인가 — Design rationale¶
순진하게 매 쿼리마다 모든 chunk 를 LLM 에 넣으면 ① context window 초과, ② 토큰 비용 폭증, ③ "lost in the middle" 로 정확도 저하 — 이 셋이 동시에 깨집니다. 그래서 (1) 의미 좌표(embedding) 로 압축하고 (2) 가까운 좌표만 빠르게 (ANN) 골라 (3) 그 부분만 LLM 에 주입하는 3 단 분리 가 RAG 시스템의 표준이 되었습니다. 이 모듈은 (1)+(2) 의 인프라, Module 04 가 (3) 까지 합치는 단입니다.
3. 작은 예 — "TLB flush" 쿼리 한 개를 end-to-end 추적¶
가장 단순한 시나리오. 사내 IP 스펙 1000 청크가 이미 인덱싱돼 있고, "TLB flush" 라는 쿼리 한 개를 던져 top-3 청크가 나오기까지를 추적합니다.
| Step | 누가 | 무엇을 | 의미 |
|---|---|---|---|
| ① | embedding 모델 | 쿼리 텍스트 → 768 차원 벡터 | 인덱싱 시 사용한 같은 모델이어야 함 (벡터 공간 일치) |
| ② | FAISS IVF | nlist=100 클러스터 중 nprobe=10 만 검색 후보로 | 전수 검색 대비 10배 빠름, 일부 누락 가능 |
| ③ | FAISS | nprobe 클러스터 안의 모든 vector 와 cosine 유사도 | dot-product or L2 — 인덱스 빌드 시 결정 |
| ④ | FAISS | top-k 반환 (k=3) | distance + index id |
| ⑤ | 호출자 | id → 원문 chunk + metadata (출처, IP 이름) 회수 | 이게 그대로 LLM prompt 의 context 가 됨 |
import faiss, numpy as np
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-large-en-v1.5")
# Step ①
query_vec = model.encode(["TLB flush"], normalize_embeddings=True) # (1, 768)
# Step ②~④ (인덱스가 IVF 라고 가정)
index = faiss.read_index("ip_spec.ivf.index")
index.nprobe = 10
distances, ids = index.search(query_vec.astype("float32"), k=3)
# Step ⑤
for d, i in zip(distances[0], ids[0]):
print(f"[{d:.3f}] {chunks[i]['text'][:80]} (src={chunks[i]['source']})")
Cosine Similarity 예시 결과:
sim("TLB invalidation", "TLB flush") = 0.95 ← 매우 유사 (의도한 검색)
sim("TLB invalidation", "TLB lookup") = 0.71 ← 비슷하지만 다른 동작
sim("TLB invalidation", "page fault") = 0.42 ← 관련 있지만 다름
sim("TLB invalidation", "boot device") = 0.08 ← 거의 무관
여기서 잡아야 할 두 가지
(1) 인덱싱 모델 ≡ 쿼리 모델 — 다른 모델을 섞으면 좌표계가 달라져 무관한 결과가 나옵니다 (§6 의 흔한 오해).
(2) ANN 은 정확 검색이 아니라 근사 검색 — IVF/HNSW 는 일부 결과를 놓칠 수 있고, 이 trade-off 가 nlist/nprobe/efSearch 같은 파라미터로 노출됩니다.
4. 일반화 — 임베딩 학습 원리 와 ANN 알고리즘 축¶
4.1 텍스트 → 벡터 변환의 본질¶
"TLB invalidation" → [0.12, -0.45, 0.78, ..., 0.33] (768~3072 차원)
"TLB flush" → [0.11, -0.43, 0.80, ..., 0.31] (유사한 벡터)
"page fault" → [0.55, 0.22, -0.10, ..., -0.67] (다른 벡터)
핵심 속성:
의미가 비슷한 텍스트 → 벡터 공간에서 가까움
의미가 다른 텍스트 → 벡터 공간에서 멀어짐
4.2 유사도 측정¶
| 메트릭 | 수식 | 특징 |
|---|---|---|
| Cosine Similarity | cos(θ) = A·B / (‖A‖×‖B‖) | 방향 기반, 크기 무관, 가장 일반적 |
| Euclidean Distance | ‖A - B‖₂ | 절대 거리, 정규화 필요 |
| Dot Product | A·B | 빠르지만 벡터 크기에 민감 |
4.3 Contrastive Learning — 임베딩이 학습되는 원리¶
핵심: "유사한 쌍은 가깝게, 다른 쌍은 멀게" 배치하는 학습
Step 1: 학습 데이터 구성
긍정 쌍 (Positive): 의미가 같은 문장 쌍
("TLB invalidation", "TLB flush operation") → 가까워야 함
부정 쌍 (Negative): 의미가 다른 문장 쌍
("TLB invalidation", "boot sequence") → 멀어야 함
Step 2: Contrastive Loss 학습
InfoNCE Loss (가장 일반적):
L = -log( exp(sim(q, k⁺)/τ) / Σ exp(sim(q, kᵢ)/τ) )
q: 쿼리 임베딩
k⁺: 긍정 샘플 임베딩
kᵢ: 전체 샘플 (긍정 + 부정) 임베딩
τ: Temperature (분포 날카로움 조절)
→ 긍정 쌍의 유사도를 높이고, 부정 쌍의 유사도를 낮추도록 학습
Step 3: Hard Negative Mining
쉬운 부정 쌍: ("TLB invalidation", "pizza recipe") → 이미 멀어서 학습 효과 적음
어려운 부정 쌍: ("TLB invalidation", "TLB lookup") → 비슷하지만 다른 의미
→ Hard Negative 를 많이 포함할수록 임베딩 품질 향상
학습 프레임워크:
- Sentence-BERT: Siamese Network + Contrastive Loss
- E5: "query: " / "passage: " 프리픽스로 비대칭 검색 최적화
- BGE: RetroMAE 사전학습 + Contrastive Fine-tuning
4.4 ANN 알고리즘의 세 갈래¶
| 알고리즘 | 자료구조 | 장점 | 단점 | 적합 규모 |
|---|---|---|---|---|
| Brute-force (Flat) | 전수 검색 | 100% 정확 | O(N) 느림 | < 100K |
| IVF (Inverted File) | K-means 클러스터링 | nlist/nprobe 로 trade-off | 학습 단계 필요 | 100K~10M |
| HNSW (Hierarchical NSW) | 다층 small-world graph | 빠르고 정확 (~97%) | 메모리 큼 | 10K~100M |
| PQ (Product Quantization) | 벡터를 sub-vector 로 양자화 | 메모리 ⅛~1/32 | 정확도 일부 손실 | 1M~10B |
각 알고리즘은 정확도 vs 속도 vs 메모리 의 다른 균형점에 위치. 소규모는 Flat, 중규모는 IVF, 고정밀이 필요하면 HNSW, 압도적 규모는 IVF+PQ 조합.
5. 디테일 — MTEB, FAISS 인덱스 종류, Chunking, 대안 Vector DB¶
5.1 주요 Embedding 모델¶
| 모델 | 차원 | 특징 | 용도 |
|---|---|---|---|
| OpenAI text-embedding-3-small | 1536 | API 기반, 편리 | 일반적 |
| OpenAI text-embedding-3-large | 3072 | 높은 정확도 | 고정밀 검색 |
| Sentence-BERT (all-MiniLM) | 384 | 오픈소스, 경량 | 로컬 배포 |
| BGE-large | 1024 | 오픈소스, 고성능 | 한국어 포함 |
| Cohere embed-v3 | 1024 | 다국어 강점 | 다국어 RAG |
5.2 MTEB 벤치마크 — Embedding 모델 선택 기준¶
MTEB (Massive Text Embedding Benchmark):
56개 데이터셋, 8개 태스크에서 Embedding 모델 종합 평가
태스크 카테고리:
- Retrieval: 검색 성능 (RAG 의 핵심)
- STS: 의미 유사도 (Semantic Textual Similarity)
- Classification: 텍스트 분류
- Clustering: 클러스터링 품질
- Reranking: 재랭킹 정확도
2024-2025 MTEB 상위 모델 (Retrieval 기준):
| 모델 | 차원 | Retrieval | 전체 평균 | 크기 |
|-----------------------|-------|-----------|-----------|---------|
| Cohere embed-v3 | 1024 | 55.0+ | 64.5+ | API |
| OpenAI text-3-large | 3072 | 54.9 | 64.6 | API |
| BGE-large-en-v1.5 | 1024 | 54.3 | 64.2 | 326MB |
| E5-mistral-7b-instruct| 4096 | 56.9 | 66.6 | 14GB |
| GTE-Qwen2-7B | 3584 | 57.0+ | 65.0+ | 14GB |
DV 도메인 선택 가이드:
보안 필수 (로컬만) + 리소스 제한: BGE-large (326MB, 고성능)
보안 필수 + GPU 여유: E5-mistral-7b (최고 성능, 도메인 적응 가능)
보안 무관 + 빠른 구현: OpenAI text-3-small (API, 간편)
다국어 필요: Cohere embed-v3 (한국어 성능 우수)
DV/EDA 도메인에서의 고려사항:
1. 도메인 적합성
- 일반 모델: "TLB" 를 일반 영어로 인식
- 도메인 특화: "TLB" 를 MMU 의 Translation Lookaside Buffer 로 인식
→ 도메인 문서로 Fine-tune 하거나, Chunk 전략으로 보완
2. 보안/프라이버시
- 반도체 IP 정보는 외부 유출 불가
- 클라우드 API (OpenAI) → 데이터 전송 필요 → 보안 위험
- 로컬 모델 (Sentence-BERT, BGE) → 사내 서버에서 실행 → 안전
→ DVCon 논문에서 보안 고려가 중요했던 이유
3. 다국어 지원
- IP 스펙: 영어
- 내부 문서: 한국어 혼용
→ 다국어 임베딩 모델 필요
5.3 FAISS (Facebook AI Similarity Search)¶
FAISS = Meta(Facebook) 가 개발한 대규모 벡터 유사도 검색 라이브러리
핵심 기능:
- 수백만~수십억 벡터에서 밀리초 단위 검색
- GPU 가속 지원
- 다양한 인덱스 타입 (정확도 vs 속도 트레이드오프)
- Python 바인딩으로 쉬운 통합
import faiss
import numpy as np
# 1. 인덱스 생성 (벡터 차원 지정)
dimension = 768
index = faiss.IndexFlatL2(dimension) # Exact search (L2 거리)
# 2. 벡터 추가
vectors = np.array([...]) # shape: (N, 768)
index.add(vectors) # N개 벡터 추가
# 3. 검색 (쿼리 벡터와 유사한 k개 반환)
query = np.array([[...]]) # shape: (1, 768)
distances, indices = index.search(query, k=5)
# → indices: 가장 유사한 5개 벡터의 인덱스
# → distances: 각각의 거리
| 인덱스 | 검색 방식 | 속도 | 정확도 | 메모리 | 용도 |
|---|---|---|---|---|---|
| IndexFlatL2 | 전수 검색 (Brute-force) | 느림 | 100% | 큼 | 소규모 (<100K) |
| IndexIVFFlat | 클러스터 기반 근사 검색 | 빠름 | ~95% | 중간 | 중규모 |
| IndexIVFPQ | 양자화 + 클러스터 | 매우 빠름 | ~90% | 작음 | 대규모 (수백만+) |
| IndexHNSW | 그래프 기반 근사 검색 | 빠름 | ~97% | 큼 | 고정밀 + 속도 |
IVF (Inverted File Index) 동작 원리:
1. 학습 단계: K-means 로 벡터들을 nlist 개 클러스터로 분류
Cluster 0: [v1, v5, v12, ...]
Cluster 1: [v2, v7, v23, ...]
...
2. 검색 단계:
쿼리 벡터 → 가장 가까운 nprobe 개 클러스터 선택
→ 해당 클러스터 내에서만 정확 검색
→ 전수 검색 대비 nlist/nprobe 배 빠름
예: nlist=100, nprobe=10 → 10배 빠르지만 일부 결과 누락 가능
DVCon 논문에서의 FAISS 활용:
IP 데이터베이스 인덱싱:
1. IP 스펙 문서 → 청크(Chunk) 로 분할
2. 각 청크 → Embedding 모델 → 벡터
3. 벡터 → FAISS 인덱스에 저장
검색 시:
"sysMMU TLB invalidation 검증 시나리오"
→ 쿼리 임베딩 → FAISS 검색 → 관련 IP 스펙 청크 반환
→ LLM 에 전달 → 검증 시나리오 생성
규모:
- 수백 개 IP 의 스펙 문서 (수천 페이지)
- 수만~수십만 청크
→ FAISS 없이는 실시간 검색 불가
5.4 Chunking 전략 — 문서를 어떻게 분할하는가¶
문제:
IP 스펙 문서 = 수백 페이지
Embedding 모델 입력 한계 = 512~8192 토큰
LLM Context 한계 = 수천~수십만 토큰
→ 문서 전체를 한 번에 Embedding 할 수 없음
→ 적절한 단위로 분할(Chunk) 하여 각각 Embedding
| 방법 | 동작 | 장단점 |
|---|---|---|
| Fixed Size | 고정 토큰 수로 분할 (512 토큰) | 간단하지만 의미 단위 무시 |
| Overlap | 고정 크기 + 겹침 영역 (50 토큰) | 경계에서 정보 손실 방지 |
| Semantic | 문단/섹션 단위로 분할 | 의미 보존, 구현 복잡 |
| Recursive | 큰 단위 → 작은 단위로 재귀 분할 | LangChain 기본, 균형 잡힘 |
DV 도메인 Chunking 고려사항:
IP 스펙 문서의 특성:
- 레지스터 정의 = 테이블 형태 → 테이블 단위 Chunk
- 프로토콜 시퀀스 = 연속적 흐름 → 시퀀스 단위 Chunk
- 블록 다이어그램 설명 = 섹션 단위 → 섹션 Chunk
IP-XACT 의 특성:
- XML 구조 → 태그 단위 파싱
- 레지스터 맵, 버스 인터페이스, 메모리 맵
→ 구조적 파싱 + 시맨틱 설명 결합 (DVCon 의 Hybrid Extraction)
from langchain.text_splitter import RecursiveCharacterTextSplitter
import re
# 방법 1: Recursive (가장 범용적)
splitter = RecursiveCharacterTextSplitter(
chunk_size=512, # 청크 최대 크기 (토큰 수 기준)
chunk_overlap=50, # 겹침 영역 (경계 정보 손실 방지)
separators=["\n## ", "\n### ", "\n\n", "\n", " "]
# 큰 구분자부터 시도 → 의미 단위 보존
)
chunks = splitter.split_text(ip_spec_text)
# 방법 2: IP-XACT 구조적 파싱 (DVCon 방식)
def chunk_ipxact(xml_root):
"""IP-XACT XML 을 의미 단위로 분할"""
chunks = []
# 레지스터 맵: 레지스터 단위
for reg in xml_root.findall('.//register'):
name = reg.find('name').text
offset = reg.find('addressOffset').text
fields = [(f.find('name').text, f.find('bitWidth').text)
for f in reg.findall('.//field')]
chunk = f"Register: {name} (offset: {offset})\nFields: {fields}"
chunks.append({"text": chunk, "type": "register", "ip": ip_name})
# 버스 인터페이스: 인터페이스 단위
for bus in xml_root.findall('.//busInterface'):
name = bus.find('name').text
bus_type = bus.find('busType').attrib
chunk = f"Bus Interface: {name}, Type: {bus_type}"
chunks.append({"text": chunk, "type": "bus_interface", "ip": ip_name})
return chunks
# 방법 3: 메타데이터 보강 Chunking
def chunk_with_metadata(text, source_file, ip_name):
"""청크에 메타데이터를 첨부하여 검색 품질 향상"""
raw_chunks = splitter.split_text(text)
enriched = []
for i, chunk in enumerate(raw_chunks):
enriched.append({
"text": chunk,
"metadata": {
"source": source_file,
"ip_name": ip_name,
"chunk_index": i,
"total_chunks": len(raw_chunks)
}
})
return enriched
Chunk Size 실험 결과 (일반적 경향):
Size = 128 토큰: 너무 작음 → 문맥 부족, 검색 정밀도 높지만 Recall 낮음
Size = 256 토큰: 짧은 정의/설명에 적합 (레지스터 정의 등)
Size = 512 토큰: 범용적 최적점 (대부분의 RAG 연구 권장)
Size = 1024 토큰: 긴 문맥 필요 시 (프로토콜 시퀀스, 동작 설명)
Size = 2048+ 토큰: Context 낭비, 관련 없는 내용 포함 위험
Overlap = Chunk Size 의 10-20% 권장
512 → overlap 50~100
1024 → overlap 100~200
DV 도메인 권장:
레지스터 정의: 256 토큰 (짧고 독립적)
프로토콜 설명: 512-1024 토큰 (문맥 필요)
IP 개요: 1024 토큰 (큰 그림)
→ 문서 타입별로 다른 Chunk Size 적용이 최적
5.5 대안: 다른 Vector DB¶
| Vector DB | 특징 | 적합한 경우 |
|---|---|---|
| FAISS | 라이브러리, 자체 서버 불필요, GPU 가속 | 임베디드, 로컬, DV 파이프라인 |
| Chroma | Python 네이티브, 간단한 API | 프로토타이핑, 소규모 |
| Pinecone | 완전 관리형 클라우드 | 프로덕션 서비스 |
| Weaviate | 하이브리드 검색 (벡터 + 키워드) | 복잡한 검색 요구 |
| Milvus | 분산, 대규모 | 수십억 벡터 |
| pgvector | PostgreSQL 확장 | 기존 RDBMS 활용 |
DVCon 논문에서 FAISS 를 선택한 이유: (1) 로컬 실행 → IP 보안 유지. (2) 라이브러리 → 별도 서버 불필요 → DV 파이프라인에 직접 통합. (3) GPU 가속 → 대규모 IP DB 에서도 빠른 검색.
6. 흔한 오해 와 DV 디버그 체크리스트¶
흔한 오해¶
❓ 오해 1 — 'Vector DB 가 모든 검색을 대체한다'
실제: 의미 검색은 정확한 키워드 매칭 (코드 식별자, 약어, 레지스터 이름) 에 약합니다. TLB_INV_CTRL 같은 사내 식별자는 임베딩 모델이 본 적 없어 generic 한 의미만 잡힙니다. Hybrid (dense + sparse/BM25) 가 production 표준.
왜 헷갈리는가: 최근 hype 로 "vector = 만능" 인상.
❓ 오해 2 — 'Embedding 모델은 차원이 클수록 좋다'
실제: 차원 ↑ → 메모리/연산 ↑ + 도메인 적합성과 무관. 1024 차원 BGE-large (326 MB) 가 4096 차원 모델보다 retrieval 점수가 높을 수도 있음. MTEB Retrieval 점수 + 도메인 적합도 가 차원보다 우선.
❓ 오해 3 — 'IVF 의 nprobe 는 클수록 정확'
실제: nprobe = nlist 면 brute-force 와 동일 (정확하지만 느림). 보통 nprobe = sqrt(nlist) 부근에서 정확도 95% 와 속도의 균형점이 있고, 그 이상은 비용만 증가.
❓ 오해 4 — '한 번 만든 인덱스는 영원히 쓴다'
실제: (1) 임베딩 모델을 교체하면 좌표계가 바뀌므로 전체 재인덱싱 필요. (2) 문서가 갱신되면 incremental update — FAISS 는 native incremental delete 가 약하므로 주기적 재빌드.
❓ 오해 5 — 'Chunk 가 작을수록 정밀'
실제: Chunk 가 너무 작으면 문맥 이 잘려 의미 임베딩이 부정확 (recall ↓). 보통 256~1024 토큰이 sweet spot. 문서 타입 에 따라 다르게 잘라야 함 (§5.4).
DV 디버그 체크리스트 (RAG 인덱스 운용 시 자주 만나는 실패)¶
| 증상 | 1차 의심 | 어디 보나 |
|---|---|---|
| 같은 검색어가 완전히 다른 결과 (이전과 비교) | 임베딩 모델 교체 후 인덱스 미재빌드 | 인덱스 메타의 model_name vs 현재 모델 |
사내 식별자 (TLB_INV_CTRL) 검색이 무관한 청크 반환 |
의미 검색만 사용 (sparse 부재) | BM25 추가 → Hybrid 로 |
| 검색이 너무 느림 (수백 ms+) | brute-force IndexFlat 사용 + N 큼 | IVF/HNSW 로 전환, nprobe 튜닝 |
| 메모리 OOM | HNSW + 큰 차원 + 많은 vector | PQ 양자화 또는 IVF+PQ |
| 청크 경계에서 정보 잘림 (recall ↓) | overlap = 0 또는 chunk_size 너무 작음 | overlap = 10-20% 로 |
| recall 은 좋은데 LLM 답변이 hallucinate | 검색 자체는 OK, LLM 단의 문제 → Module 04 §6 | |
| 다국어 (한/영) 혼용 검색 품질 낮음 | 단일 언어 모델 사용 | Cohere embed-v3 / BGE-M3 같은 다국어 모델 |
| 인덱스 빌드 후 검색이 원하는 결과를 전혀 못 찾음 | embed normalize 안 함 + cosine vs L2 불일치 | normalize_embeddings=True + IndexFlatIP 매칭 |
7. 핵심 정리 (Key Takeaways)¶
- Embedding = 의미를 좌표로 — 의미가 비슷한 텍스트가 가까운 벡터.
- ANN 알고리즘 — IVF (cluster), HNSW (graph), PQ (compression). 선택은 데이터 규모와 메모리 예산.
- FAISS — Facebook AI 의 표준 ANN 라이브러리. 수십~수억 벡터를 단일 머신에서 처리.
- 임베딩 모델 선정 — MTEB 벤치마크 + 도메인 특화 fine-tune 검토 + 보안 (로컬/API).
- 품질 평가 — top-k recall, MRR, nDCG. retrieval 이 망가지면 LLM 이 아무리 좋아도 답이 망가진다.
실무 주의점 — Embedding 모델 교체 시 기존 인덱스 전체 재구축 필요
현상: RAG 운영 중 더 성능 좋은 Embedding 모델로 교체하면, 기존 벡터 인덱스와 새 모델의 벡터 공간이 달라 검색 결과가 완전히 깨진다. 오류 없이 응답이 나오지만 관련 없는 문서가 검색되어 hallucination 이 증가한다.
원인: 각 Embedding 모델은 고유한 벡터 공간을 가지며, 모델이 다르면 같은 문장도 다른 방향의 벡터로 인코딩된다.
점검 포인트: 모델 교체 후 기존 인덱스의 model_name 메타데이터와 현재 사용 모델이 일치하는지 확인. Retrieval 정확도 지표(Top-5 Recall) 를 교체 전후 비교하고, 불일치 시 전체 문서에 대해 재임베딩 및 인덱스 재구축 필수.
7.1 자가 점검¶
🤔 Q1 — Chunking 전략 (Bloom: Apply)
당신은 SystemVerilog 코드 base 를 RAG 로 색인. 한 파일 1000 줄. 어떻게 chunk?
정답
AST 기반 chunking (semantic). 함수/class/module 단위로 분할.
Naive (고정 크기, 예: 500 token) 의 문제:
- 함수 중간에서 잘림 → chunk 하나는 signature 만, 다른 하나는 body 만 → retrieval 시 어느 것도 충분하지 않음.
- Module 의 begin/end 가 다른 chunk 로 분리 → 컨텍스트 불완전.
AST chunking 의 장점: 각 chunk 가 self-contained. 단점: 큰 함수가 한 chunk 에 다 안 들어갈 수도 있음 → recursive split 필요.
🤔 Q2 — Recall vs Precision (Bloom: Evaluate)
당신의 RAG 가 top-k=10 으로 운영. Recall 은 좋은데 LLM 답변 품질 이 낮다. 무엇을 시도해야 하나?
정답
Top-k=10 이라 noisy chunks 가 LLM context 에 들어감 → "lost in the middle" 발생. 시도: 1. Reranker 추가: top-k=50 으로 retrieve → cross-encoder reranker 로 top-5 만 추림 → LLM 으로. 2. Hybrid search: BM25 + embedding score fusion → precision 향상. 3. MMR (Maximal Marginal Relevance): 다양성 + 관련성 balance.
모두 precision 을 올리는 방향. Recall 은 첫 retrieval step 에서 충분하면 됨.
🤔 Q3 — Embedding 모델 선택 (Bloom: Analyze)
당신은 사내 SystemVerilog/UVM 코드 RAG. 두 옵션:
- (a) OpenAI text-embedding-3-large (API, 3072-dim).
- (b) 사내 BGE / E5 (로컬, 1024-dim).
어떤 것을 선택? 3 가지 차원 으로 분석.
정답
| 차원 | (a) API | (b) 로컬 |
|---|---|---|
| 보안 | 사내 코드가 외부 API 로 전송 → IP leak risk | 완전 격리 |
| 품질 | MTEB 상위, 범용 학습 잘됨 | 도메인 fine-tune 가능, SV/UVM 특화 가능 |
| 비용 | $0.13/1M token (운영비) | GPU 1 회성 비용 |
| 운영 | 인덱스 재구축 시 시간↑ | offline 처리 가능 |
보안이 decisive 라면 (b). 보안 OK 면 둘 다 시도 후 MTEB-DV subset 으로 비교.
7.2 출처¶
Internal (Confluence)
- 5. KV Caching & VectorDB w/ MangoBoost (id=613187588)
- Design Document of Component/System-Level Benchmarking Tool (id=613482498)
External - MTEB: Massive Text Embedding Benchmark — Muennighoff et al., 2023 - Lost in the Middle: How Language Models Use Long Contexts — Liu et al., TACL 2023 - FAISS: A library for efficient similarity search — Johnson et al., 2017 - HNSW: Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs — Malkov & Yashunin, 2016 - BGE M3-Embedding — Chen et al., 2024
다음 모듈¶
→ Module 04 — RAG (Retrieval-Augmented Generation): embedding/vector DB 를 LLM 호출과 결합.