Field Log · Entry

실전: 재현 가능한 Embedding 연구 Pipeline 만들기 (14/14)

Corpus와 qrel 동결에서 embedding 대조 학습, exact 평가, 압축과 ANN, RAG 검증, shadow index 배포와 rollback으로 이어지는 실전 pipeline

이번 글의 결과물

  • corpus·query·qrel·split을 immutable snapshot으로 만들고 contamination을 검사합니다.
  • 공개 model zero-shot, BM25, hybrid를 고정 baseline으로 둔 뒤 pair/triplet contrastive fine-tuning을 실행합니다.
  • Hard negative는 mine한 뒤 teacher·human audit으로 false negative를 제거하고, round마다 새 version으로 저장합니다.
  • Full-precision exact 평가에서 model을 비교한 뒤 dimension·quantization·ANN 손실을 순서대로 더합니다.
  • Paired 통계, 한국어·길이·부정·multi-hop slice, downstream RAG utility, latency·security gate를 모두 통과한 index만 canary로 배포합니다.

앞 글까지 embedding 연구의 기하, 학습, 평가, 최신 흐름을 분해했습니다. 마지막 편에서는 이를 하나의 재현 가능한 연구 pipeline으로 다시 조립합니다.

frozen data
  → zero-shot baseline
  → train data audit
  → contrastive training
  → exact evaluation
  → dimension / quantization
  → ANN + filter load test
  → fixed RAG reader
  → shadow index / canary / rollback

이 글의 code는 구조를 보여 주는 최소 예제입니다. 실제 실행에서는 명시한 package version, model license, hardware에 맞춰 lock file과 test를 유지합니다.


1. 성공 조건을 한 문장으로 쓴다

나쁜 목표:

"우리 domain에서 좋은 embedding을 만든다."

검증 가능한 목표:

한국어 정책 질의에서 candidate Recall@50을 baseline보다 2%p 이상 높이고,
negation·최신 규정 slice regression은 1%p 이내로 제한하며,
ANN recall@50 ≥ 0.96, query p95 ≤ 35ms,
index RAM ≤ 80GB, fixed reader answer correctness를 낮추지 않는다.

Quality, regression, latency, memory, downstream 조건이 함께 있어야 “가장 높은 score”가 아니라 “배포 가능한 개선”을 찾습니다.

2. 실험 Directory

embedding-lab/
├── configs/
│   ├── data-v5.yaml
│   ├── train-e5-domain-v3.yaml
│   └── eval-v8.yaml
├── data/
│   ├── manifests/
│   └── samples/              # 민감하지 않은 작은 fixture만
├── src/
│   ├── build_snapshot.py
│   ├── audit_pairs.py
│   ├── mine_negatives.py
│   ├── train.py
│   ├── encode.py
│   ├── evaluate_exact.py
│   ├── evaluate_ann.py
│   └── evaluate_rag.py
├── tests/
│   ├── test_ids.py
│   ├── test_metrics.py
│   └── test_model_contract.py
├── reports/
│   └── run-2026-07-20-001/
├── requirements.lock
└── README.md

대용량 corpus·vector는 Git에 넣지 않습니다. Manifest와 checksum, 접근 가능한 object-store URI를 versioning합니다.

3. Data Contract

Corpus

{
  "doc_id": "policy:refund:2026-03#section-4#chunk-02",
  "parent_id": "policy:refund:2026-03#section-4",
  "title": "환불 정책",
  "heading": "처리 기한",
  "text": "...",
  "language": "ko",
  "valid_from": "2026-03-01",
  "valid_to": null,
  "acl_scope": "employee",
  "source_revision": "sha256:...",
  "chunker_revision": "heading-v3"
}

Query

{
  "query_id": "q-008421",
  "text": "승인된 환불은 언제 입금돼?",
  "language": "ko",
  "timestamp": "2026-06-14T08:21:00Z",
  "source": "anonymized-human",
  "group_id": "session-31f...",
  "slices": ["policy", "temporal", "colloquial"]
}

Qrel

{
  "query_id": "q-008421",
  "doc_id": "policy:refund:2026-03#section-4#chunk-02",
  "relevance": 3,
  "evidence_set_id": "ev-008421-a",
  "annotator_count": 2,
  "adjudicated": true
}

ID는 row number가 아니라 source와 version에 연결합니다. Chunk text가 달라지면 같은 ID를 재사용하지 않습니다.

4. Split은 Query Random Split보다 먼저 설계한다

같은 source document에서 생성한 유사 query가 train과 test에 나뉘면 성능이 부풀 수 있습니다.

group key candidates:
  source_document
  source_thread / user session
  answer entity
  template family
  temporal window

권장 split:

split_policy:
  train: "documents valid before 2026-01-01"
  dev: "2026-01-01 through 2026-03-31"
  test: "2026-04-01 through 2026-06-30"
  group_by: [source_family, normalized_answer]
  hidden_test: true

과거 문서에만 답이 있는 질의와 최신 문서가 정답인 질의를 별도 slice로 둡니다.

5. Contamination Audit

Exact

normalize Unicode / whitespace / boilerplate
  → hash query and document spans
  → train↔dev/test duplicate check

Near Duplicate

  • character/token MinHash
  • BM25 top neighbor
  • 독립 embedding model의 cosine neighbor
  • title·entity·answer overlap

Provenance

  • 같은 source dump·revision 여부
  • translated/paraphrased variant
  • synthetic prompt template family
  • public benchmark 포함 여부
  • teacher model이 평가 data를 학습했을 가능성 disclosure

제거 기준과 남긴 예외를 report에 기록합니다. Audit model이 test를 학습 model에 직접 노출하는 방식도 피합니다.

6. Label Quality를 측정한다

Qrel은 정답 파일이 아니라 관찰입니다.

sample:
  100 easy + 100 hard + 100 disagreement + 100 no-answer

measure:
  annotator agreement
  adjudication change rate
  positive completeness
  stale-label rate

특히 retrieval에서는 unlabeled relevant document가 negative로 취급되기 쉽습니다. Pooling 방식으로 BM25, 여러 dense model, reranker의 상위 후보를 합쳐 annotation pool을 넓힙니다.

7. Baseline을 네 개 고정한다

B0  BM25
B1  public multilingual embedding + fp32 exact
B2  BM25 + dense reciprocal rank fusion
B3  dense top-N + fixed cross-encoder reranker

Fine-tuned model이 B1만 이기고 B2/B3를 이기지 못할 수 있습니다. 그 경우 model 자체는 개선됐지만 system 투자 우선순위는 다를 수 있습니다.

Baseline contract:

model_contract:
  id: org/model
  revision: exact-commit
  tokenizer_revision: exact-commit
  query_template: "query: {text}"
  document_template: "passage: {title}\n{heading}\n{text}"
  max_query_tokens: 256
  max_document_tokens: 1024
  truncation_side: right
  pooling: official
  normalize: true
  dimension: 768
  similarity: inner_product

이 contract는 학습, corpus encoding, online query encoding에서 하나의 source를 사용합니다.

8. Pair의 의미를 먼저 정의한다

Contrastive loss보다 중요한 것은 row의 의미입니다.

anchor:   실제 query
positive: query를 충분히 해결하는 최신·권한 내 chunk
negative: topic은 비슷하지만 해당 조건을 충족하지 않는 chunk

Negative taxonomy:

종류예시학습 목적
Random전혀 다른 domain초기 separation
Lexical hard단어는 같지만 답이 다름keyword shortcut 억제
Dense hard의미가 가까운 오답local boundary
Temporal폐기된 과거 규정freshness 구분
Negation허용/금지 반대polarity
Entity비슷한 상품·법령 번호identity precision
Near-positive일부만 답함graded relevance

Near-positive를 무조건 binary negative로 넣지 않습니다. Multi-positive나 soft/graded supervision을 검토합니다.

9. 학습 Recipe를 단계화한다

Stage 0  zero-shot checkpoint
Stage 1  broad clean pairs, in-batch negatives
Stage 2  mined + relabeled domain negatives
Stage 3  optional teacher margin/listwise distillation
Stage 4  optional MRL multi-dimension objective

처음부터 모든 technique를 섞지 않습니다. Stage마다 exact dev metric과 critical regression을 저장해 gain 출처를 확인합니다.

10. 최소 Contrastive Training 예제

아래 예시는 2026년 7월의 Sentence Transformers 5.x Trainer 계열 API를 기준으로 한 구조입니다. 재현 실행에서는 sentence-transformers==5.5.0처럼 exact version과 transitive dependency를 lock합니다.

from datasets import Dataset
from sentence_transformers import (
    SentenceTransformer,
    SentenceTransformerTrainer,
    SentenceTransformerTrainingArguments,
    losses,
)
from sentence_transformers.training_args import BatchSamplers

model = SentenceTransformer(
    "intfloat/multilingual-e5-base",
    revision="PIN_A_REAL_MODEL_COMMIT",
)

# Trainer가 text column을 model input으로 해석하므로
# ID·metadata는 별도 table에 두고 input column 순서를 test한다.
train_dataset = Dataset.from_dict(
    {
        "anchor": [
            "query: 승인된 환불은 언제 입금돼?",
            "query: 해외 배송비는 환불되나요?",
        ],
        "positive": [
            "passage: 환불 승인 후 영업일 기준 3~5일 이내 지급됩니다.",
            "passage: 상품 하자 시 해외 배송비를 포함해 환불합니다.",
        ],
        "negative": [
            "passage: 환불 신청은 구매 후 14일 이내 가능합니다.",
            "passage: 단순 변심의 국제 반송비는 구매자가 부담합니다.",
        ],
    }
)

loss = losses.MultipleNegativesRankingLoss(model)

args = SentenceTransformerTrainingArguments(
    output_dir="runs/e5-domain-v3",
    num_train_epochs=1,
    per_device_train_batch_size=64,
    gradient_accumulation_steps=4,
    learning_rate=2e-5,
    warmup_ratio=0.1,
    bf16=True,
    batch_sampler=BatchSamplers.NO_DUPLICATES,
    save_strategy="steps",
    save_steps=500,
    logging_steps=20,
    seed=17,
)

trainer = SentenceTransformerTrainer(
    model=model,
    args=args,
    train_dataset=train_dataset,
    loss=loss,
)
trainer.train()

실제 학습 전 smoke test:

  • 32개 row overfit 가능 여부
  • positive score가 negative보다 증가하는지
  • input prefix가 중복되지 않는지
  • long input truncation rate
  • batch 안 duplicate positive 비율
  • gradient NaN과 effective batch size

NO_DUPLICATES sampler는 in-batch negative와 같은 text가 겹치는 문제를 줄이지만 semantic false negative를 자동 제거하지는 않습니다.

11. Loss 선택

Data첫 loss주의
(q, positive) pairsMultiple Negatives Ranking / InfoNCEin-batch false negative
(q, pos, neg)InfoNCE with explicit negativeshardness·label audit
Cross-encoder scoresMarginMSE / KL distillationteacher calibration
Graded relevancelistwise/pairwise rank objectivegroup construction
Multi-dimensionMatryoshka wrapper/objectivedimension별 weight
Parallel language pairscontrastive + distillationsemantic drift

Loss 이름으로 결과를 예측하지 않습니다. 대조 학습 편의 temperature·batch·normalization을 함께 기록합니다.

12. Effective Batch와 GradCache

InfoNCE는 비교하는 negative 수가 중요합니다. Gradient accumulation은 optimizer batch를 늘리지만, 각 forward에서 보는 in-batch negative 수를 자동으로 늘리지 않습니다.

micro batch 64 × accumulation 4
  optimizer effective batch = 256
  contrastive candidate pool = usually 64 per forward

Memory가 제한되면 cached multiple-negatives/GradCache 계열 loss를 검토합니다. Report에는 optimizer effective batchcontrastive negative pool을 따로 씁니다.

Multi-GPU에서 cross-device negative를 gather하는지, positive pair가 다른 rank에서 false negative가 되지 않는지도 확인합니다.

13. Hard-negative Mining Round

checkpoint r0
  → encode train queries and allowed corpus
  → retrieve top-200
  → remove known positives / same evidence set
  → teacher or human relabel top candidates
  → construct easy+hard mixture
  → train checkpoint r1

Mining record:

{
  "query_id": "q-008421",
  "doc_id": "policy:refund:2024#section-4",
  "miner_model": "run-r0",
  "miner_rank": 3,
  "miner_score": 0.873,
  "teacher_model": "pinned-reranker",
  "teacher_score": 0.42,
  "label": "temporal_negative",
  "audit": "human-confirmed"
}

Miner checkpoint와 corpus snapshot이 없으면 negative dataset을 재현할 수 없습니다.

14. False-negative Guard

다음 조건이면 자동 negative에서 제외하거나 review queue로 보냅니다.

  • 같은 evidence_set_id
  • 같은 parent의 겹치는 chunk
  • known answer string 포함
  • 여러 teacher가 relevant로 판단
  • top lexical과 dense가 동시에 매우 높음
  • qrel에 없지만 최신 문서가 query를 직접 답함
  • annotation disagreement가 큼

Teacher threshold는 dev metric이 아니라 human-audited precision/recall sample로 정합니다.

negative retention rate
false-negative rate by miner rank
human overturn rate
downstream gain per retained example

이 네 값을 함께 보고합니다.

15. Exact Encoding Artifact

Corpus vector에는 순서가 아니라 ID mapping을 저장합니다.

vectors.f16.npy
doc_ids.parquet
embedding_manifest.yaml

assert vectors.shape[0] == len(doc_ids)
assert unique(doc_ids) == len(doc_ids)

Sample determinism test:

import numpy as np

def assert_normalized(vectors, atol=1e-3):
    norms = np.linalg.norm(vectors.astype("float32"), axis=1)
    np.testing.assert_allclose(norms, 1.0, atol=atol)

def assert_same_sample(old, new, atol=1e-5):
    np.testing.assert_allclose(old, new, atol=atol, rtol=0)

GPU kernel·dtype 차이로 bitwise identity가 어려울 수 있으므로 허용 오차와 ranking stability를 함께 사용합니다.

16. Exact Retrieval부터 계산한다

import numpy as np

def exact_topk(query_vectors, document_vectors, k=100, block=50_000):
    """L2-normalized vector의 inner product top-k; production용 최적화 코드는 아님."""
    all_scores = np.full((len(query_vectors), k), -np.inf, dtype=np.float32)
    all_ids = np.full((len(query_vectors), k), -1, dtype=np.int64)

    for start in range(0, len(document_vectors), block):
        docs = document_vectors[start : start + block].astype(np.float32)
        scores = query_vectors.astype(np.float32) @ docs.T

        local_k = min(k, scores.shape[1])
        local = np.argpartition(scores, -local_k, axis=1)[:, -local_k:]
        local_scores = np.take_along_axis(scores, local, axis=1)
        local_ids = local + start

        merged_scores = np.concatenate([all_scores, local_scores], axis=1)
        merged_ids = np.concatenate([all_ids, local_ids], axis=1)
        keep = np.argpartition(merged_scores, -k, axis=1)[:, -k:]
        all_scores = np.take_along_axis(merged_scores, keep, axis=1)
        all_ids = np.take_along_axis(merged_ids, keep, axis=1)

    order = np.argsort(-all_scores, axis=1)
    return (
        np.take_along_axis(all_ids, order, axis=1),
        np.take_along_axis(all_scores, order, axis=1),
    )

이 구현은 작은 연구 corpus에서 검산하기 위한 것입니다. Tie-breaking을 deterministic하게 만들고, toy fixture에서 exhaustive Python sort와 결과가 같은지 test합니다.

17. Primary Metric을 Candidate 역할에 맞춘다

Reranker 앞 candidate generator라면:

primary:   Recall@50 or Recall@100
secondary: nDCG@10, MRR@10
evidence:  complete-evidence@N for multi-hop

사용자에게 dense result를 바로 노출한다면 nDCG·precision의 비중이 커집니다.

모든 metric 규약:

  • positive 없는 query 처리
  • graded relevance gain
  • unjudged document 처리
  • child→parent deduplication
  • tie-breaking
  • query macro average

를 code test와 report에 고정합니다.

18. Paired Statistics와 Regression Budget

각 query에서 challenger와 baseline metric delta를 만듭니다.

Δ_i = metric_i(challenger) - metric_i(baseline)

Session이나 template family가 상관돼 있으면 query가 아니라 group 단위 paired bootstrap을 사용합니다.

Release rule 예:

statistical_gate:
  primary_mean_delta_min: 0.02
  primary_ci_lower_min: 0.005
  critical_slice_delta_min: -0.01
  worst_query_review_count: 100
  bootstrap_groups: 10000
  seed: 17

P-value 하나보다 effect size, interval, win/tie/loss, worst regression을 같이 봅니다.

19. Slice Dashboard

language:        Korean / English / mixed
query style:     keyword / natural / conversational / reasoning
match:           lexical / paraphrase / entity / number / negation
document:        short / long / truncated / context-dependent
evidence:        single / multi-hop / no-answer
temporal:        current / stale-conflict
risk:            normal / policy / security
source:          human / synthetic

각 slice에 sample 수와 confidence interval을 표시합니다. 작은 slice의 점 추정치만 보고 model을 기각하거나 승인하지 않습니다.

20. Error Taxonomy

Top regression을 다음으로 분류합니다.

실패다음 실험
Gold가 corpus에 없음ingestion/qrel 수정
Chunk에 답이 잘림chunker/parent retrieval
Exact lexical entity 누락BM25/hybrid·entity negatives
의미는 비슷한 오답hard-negative 정제
과거 규정이 상위temporal metadata·negative
부정 반전negation slice/data
긴 chunk 후반 누락truncation·late chunking
한국어 colloquial 실패native query data
Exact 성공, ANN 실패index tuning
Retrieval 성공, 답 실패context packing/reader

모든 실패를 fine-tuning 문제로 만들지 않습니다.

21. Ablation은 한 축씩

E0  zero-shot
E1  + clean domain pairs
E2  + mined/relabelled negatives
E3  + teacher distillation
E4  + MRL
E5  + contextual chunk encoding

각 run은 이전 run에서 한 축만 바꿉니다. Computing budget이 제한되면 작은 frozen dev corpus에서 screening한 뒤 finalist만 full corpus로 encode합니다.

Run table:

RunChangeExact R@50Korean hardp95 encodeDecision
E0baselinekeep
E1data
E2negatives
E3distill
E4MRL

22. Dimension·Quantization 단계

Exact full-precision winner에서만 compression sweep을 시작합니다.

full 768d fp32 exact
  → 512 / 256d fp32 exact
  → selected dimension fp16 / int8 exact
  → selected representation ANN

각 단계의 delta를 저장합니다.

compression_report:
  full_fp32_exact: 0.842
  reduced_fp32_exact: 0.838
  reduced_int8_exhaustive: 0.835
  reduced_int8_ann: 0.831

이 숫자는 예시 형식이며 실제 결과가 아닙니다.

23. ANN Parameter Sweep

hnsw_grid:
  M: [16, 32]
  efConstruction: [128, 256]
  efSearch: [32, 64, 128, 256]

Offline:

  • ANN recall@10/50/100
  • qrel Recall/nDCG
  • memory·index build time

Load test:

  • concurrency 1/16/64
  • p50/p95/p99
  • QPS at target ANN recall
  • filter selectivity 100/10/1/0.1%
  • cold/warm cache

Target recall 아래 parameter는 latency가 빨라도 제거합니다.

24. Hybrid와 Reranker를 다시 붙인다

Embedding의 독립 gain과 system gain을 둘 다 봅니다.

S0  BM25
S1  dense baseline
S2  dense challenger
S3  BM25 + dense baseline
S4  BM25 + dense challenger
S5  S3 + fixed reranker
S6  S4 + same fixed reranker

S2 > S1인데 S6 ≈ S5라면 reranker가 차이를 흡수했거나 hybrid 후보가 이미 포화됐을 수 있습니다. Candidate recall·reranker input overlap을 분석합니다.

25. Fixed-reader RAG Gate

Generator, prompt, context budget, reranker, sampling seed를 고정합니다.

retrieval baseline ─┐
                    ├─ same context selector → same reader → answers
retrieval challenger┘

평가:

  • answer correctness
  • claim support
  • citation precision·recall
  • complete evidence coverage
  • no-answer correctness
  • context token 수
  • total p95·cost

LLM judge만 쓰지 말고 answerable subset의 human audit와 deterministic evidence metric을 함께 둡니다.

26. Security·Privacy Gate

ACL Isolation

다른 tenant의 relevant vector가 top-k에 섞여도 metadata 단계에서 절대 반환되지 않는지 test합니다. Post-filter로 k가 부족할 때 다른 tenant 결과로 채우지 않습니다.

Deletion

삭제 요청 뒤 online result, cache, replica, backup retention 상태를 추적합니다.

Inversion Probe

허가된 red-team 환경에서 vector만으로 민감 attribute나 text를 추정할 수 있는지 model version별로 점검합니다.

Poisoning

Keyword stuffing, duplicate flood, Unicode 변형, instruction injection 문서를 corpus에 넣어 critical query의 rank 변화를 측정합니다.

27. Run Manifest

run:
  id: emb-2026-07-20-001
  objective: korean-policy-retrieval
  code_commit: abc123
  environment:
    python: "3.12.x"
    lockfile_sha256: "..."
    cuda: "..."
    gpu: "..."
  seeds:
    train: 17
    mining: 23
    evaluation: 47
  data:
    corpus_manifest: corpus-v12
    train_pairs: pairs-v7
    dev_queries: dev-v5
    test_queries: hidden-test-v3
    qrels: qrels-v8
  model:
    base_revision: "..."
    output_checkpoint: "..."
    prompt_revision: prompt-v4
    pooling: mean
  training:
    loss: multiple-negatives-ranking
    temperature_or_scale: 20
    optimizer_batch: 256
    contrastive_pool: 64
    epochs: 1
  evaluation:
    metric_code_commit: def456
    exact_dtype: float32
    bootstrap_groups: 10000

Manifest는 결과 표의 각 row로 역추적할 수 있어야 합니다.

28. Model Card에 추가할 내용

  • Intended use와 out-of-scope use
  • Training data source·기간·license
  • Synthetic generator와 teacher provenance
  • Negative mining·relabel policy
  • 언어·domain·길이 coverage
  • Input prompt·pooling·normalization
  • 지원 dimension·dtype
  • Benchmark version과 contamination disclosure
  • Exact/ANN·hardware result
  • Known failure: negation, number, context, modality
  • Privacy·security 고려
  • Re-index migration requirement

Model weight만 공개하고 사용 contract를 숨기면 결과를 재현할 수 없습니다.

29. Shadow Index와 Canary

offline pass
  → build index v12
  → checksum + ID coverage
  → replay logged queries, no user-visible effect
  → compare overlap, score, latency, ACL
  → 1% canary
  → 10% / 50% / 100%

Canary metric:

  • zero-result rate
  • top-k overlap과 rank flip
  • click/answer/citation proxy
  • p95/p99·timeout
  • tenant leakage 0
  • query/index version mismatch 0

이전 index와 query encoder를 rollback window 동안 함께 보존합니다.

30. Release Decision Template

decision:
  candidate: emb-domain-v3-index-v12
  status: ship | hold | reject
  evidence:
    exact_primary_delta: "+..."
    paired_ci: ["...", "..."]
    critical_worst_delta: "..."
    ann_recall_at_50: "..."
    p95_ms: "..."
    ram_gb: "..."
    rag_answer_delta: "..."
    security_gate: pass
  known_risks:
    - "..."
  rollback:
    index: v11
    query_encoder: emb-domain-v2
  approvers:
    - retrieval-owner
    - data-owner
    - security-owner

hold는 실패가 아니라 필요한 추가 증거가 명확한 상태입니다.

31. 실패를 빠르게 찾는 Decision Tree

Exact metric가 낮다?
  ├─ yes → corpus/qrel 문제인가?
  │          ├─ yes → data/chunk 수정
  │          └─ no  → prompt/pooling/data/loss 분석
  └─ no  → compressed exact가 낮다?
             ├─ yes → dimension/quantizer 완화
             └─ no  → ANN이 낮다?
                        ├─ yes → index/search parameter
                        └─ no  → RAG가 낮다?
                                   ├─ yes → packing/reranker/reader
                                   └─ no  → latency·cost·security gate

이 분해가 embedding 연구를 “checkpoint를 계속 바꿔 보는 일”에서 system science로 바꿉니다.

32. 4주 학습 Project

1주차 · 재현

  • 공개 model 두 개의 공식 prompt를 재현
  • 500 query·10만 document exact 평가
  • BM25/dense/hybrid baseline
  • metric unit test

2주차 · 학습

  • human/domain pair 정제
  • InfoNCE baseline
  • hard-negative mining과 100개 audit
  • temperature·batch ablation

3주차 · 평가

  • paired bootstrap
  • 한국어·부정·숫자·긴 문서 slice
  • public benchmark 한 suite
  • error taxonomy 100건

4주차 · 서빙

  • MRL dimension sweep
  • int8/PQ 한 방식
  • HNSW target-recall sweep
  • fixed-reader RAG와 shadow index report

각 주의 결과는 score가 아니라 manifest, report, 실패 분석, 다음 hypothesis입니다.

33. 최종 Checklist

문제·데이터

  • 성공 조건에 quality·cost·regression이 있다.
  • Corpus·query·qrel·split을 snapshot과 checksum으로 고정했다.
  • Exact·near-duplicate·source·process contamination을 검사했다.
  • Qrel completeness와 annotator disagreement를 샘플링했다.

학습

  • Prompt·pooling·normalization contract가 일관된다.
  • Pair/triplet의 relevance 의미가 명확하다.
  • Hard negative를 relabel하고 false-negative rate를 냈다.
  • Optimizer batch와 contrastive pool을 구분했다.
  • Stage별 ablation으로 gain 출처를 확인했다.

평가·서빙

  • Full-precision exact에서 먼저 비교했다.
  • Paired interval과 critical slice를 보고했다.
  • Dimension·quantization·ANN loss를 분해했다.
  • Target ANN recall에서 latency·memory를 비교했다.
  • Hybrid·reranker·fixed reader에서 system gain을 확인했다.

운영

  • Model/index identity와 compatibility check가 있다.
  • ACL·삭제·inversion·poisoning test를 통과했다.
  • Shadow·canary·rollback 절차가 있다.
  • Model card와 run manifest로 결과를 재현할 수 있다.

스스로 확인하기

  1. Query random split보다 source·time group split이 더 안전한 이유는 무엇인가?
  2. Gradient accumulation이 contrastive negative pool을 자동으로 늘리지 않는 이유는 무엇인가?
  3. Hard negative teacher threshold를 dev metric만으로 정하면 어떤 문제가 생기는가?
  4. Fine-tuned embedding이 exact에서 좋아졌지만 RAG 답변은 같을 때 어디를 분석해야 하는가?
  5. Full-precision exact, compressed exact, ANN을 순서대로 평가하면 어떤 원인을 분리할 수 있는가?
  6. Model update 때 shadow index와 이전 query encoder를 함께 보존해야 하는 이유는 무엇인가?

이것으로 14편의 Embedding 연구·엔지니어링 시리즈를 마칩니다. 다시 볼 때는 목적에 따라 다음 경로를 권합니다.

Reranker까지 함께 연구하려면 Reranker 엔지니어링 첫 글production reranking 실습을 이어서 읽으면 됩니다.

참고자료