콘텐츠로 이동

토크나이저와 임베딩

분류: Layer 11 - AI 기초 & 머신러닝 | 선수지식: L11-10 (ML 수학 토대), L11-50 (트랜스포머)

토크나이저와 임베딩 — BPE, 임베딩 공간, 의미 거리

섹션 제목: “토크나이저와 임베딩 — BPE, 임베딩 공간, 의미 거리”

**토크나이저(tokenizer)**는 텍스트를 모델이 읽는 작은 조각인 **토큰(token)**의 ID 시퀀스로 바꾸는 모듈이다. **임베딩(embedding)**은 토큰·문장·이미지 같은 대상을 의미가 비슷한 것끼리 가까운 고차원 벡터로 바꾸는 표현 방식이다.

두 개념은 한 흐름에 있다. 토크나이저가 먼저 “무엇을 모델 입력 단위로 볼 것인가”를 정하고, 임베딩은 그 단위를 “어떤 좌표로 놓을 것인가”를 정한다. 그래서 LLM 비용, context window 실효 길이, fine-tuning 안정성, RAG 검색 품질이 모두 이 토픽 위에 올라간다.

처음에는 토크나이저와 임베딩을 별도 기능처럼 보기 쉽다. 실제 운영에서는 세 질문으로 계속 다시 만난다.

  1. 이 텍스트는 몇 토큰인가? API 가격과 context window는 토큰 단위로 움직인다. 같은 의미라도 한국어가 영어보다 더 많은 토큰으로 쪼개지면 비용과 최대 입력 길이가 달라진다.
  2. 새 단어와 고유명사를 모델이 잃지 않는가? word-level 토크나이저는 학습 어휘에 없는 단어를 OOV(out-of-vocabulary, 어휘 밖 단어)로 떨어뜨렸다. BPE 계열은 이 문제를 subword 조합으로 줄였고, byte-level BPE는 모든 unicode를 byte 시퀀스로 표현해 OOV를 원천 제거한다.
  3. 검색 결과가 의미 기준으로 가까운가? RAG는 질문과 문서를 벡터로 바꾼 뒤 거리로 찾는다. 거리 정의, 임베딩 모델, reranker(후보를 다시 점수화하는 모델), hybrid search(BM25 같은 키워드 검색과 dense 검색 결합) 선택이 틀리면 검색은 조용히 나빠진다.

이 문서의 목표는 도구 이름을 외우는 것이 아니다. “텍스트가 토큰이 되고, 토큰·문장이 벡터가 되고, 벡터 거리가 검색 품질과 비용 판단으로 이어진다”는 흐름을 손으로 설명할 수 있게 만드는 것이다.

2.5 선행 한계 — word-level OOV와 word2vec polysemy가 만든 토픽

섹션 제목: “2.5 선행 한계 — word-level OOV와 word2vec polysemy가 만든 토픽”

frontmatter lineage_oneliner: "토큰화 OOV 문제를 BPE로 해결하고 임베딩으로 의미거리 기반 검색 지원"의 본문 근거.

토크나이저 계보 — word-level → BPE → byte-level BPE

섹션 제목: “토크나이저 계보 — word-level → BPE → byte-level BPE”
  • Word-level의 한계: 단어 하나를 token 하나로 두면 vocab이 폭증하고, 학습 코퍼스에 없던 단어는 [UNK]가 된다. 이 OOV 문제 때문에 신조어·고유명사·오타를 만날 때마다 의미가 사라질 수 있다.
  • BPE(Byte Pair Encoding)의 등장 (Sennrich et al. 2015): 자주 붙어 나오는 문자·byte 쌍을 반복 합병해 subword token을 만든다. 새 단어도 여러 subword 조각으로 표현할 수 있어, 어휘 크기를 고정하면서 OOV를 줄인다. 단 vanilla BPE는 시작 단위가 vocab에 없으면 여전히 OOV가 남을 수 있고, byte-level BPE가 이 경계를 다시 줄인다. WMT15 EN→DE에서 dictionary back-off 대비 +1.1 BLEU, EN→RU +1.3 BLEU (arXiv:1508.07909).
  • byte-level BPE (Radford et al. 2019, GPT-2): 기본 단위를 unicode 문자가 아니라 UTF-8 byte 256개로 둔다. 모든 unicode 문자는 byte 조합으로 표현 가능하므로 OOV가 원천 제거된다. 대규모 코퍼스에서는 unique unicode 문자만으로 ~5K가 일반 32K BPE vocab의 상당 비중을 차지한다는 문제가 있었고, GPT-2는 이를 byte-level로 우회했다 (openai/gpt-2 encoder.py bytes_to_unicode() 주석). GPT-2 50,257 vocab, GPT-4o ~200k vocab(§3.4)이 모두 이 흐름 위에 있다.

핵심은 “단어를 외워서 처리한다”에서 “단어를 조합 가능한 부품으로 분해한다”로 바뀐 점이다. 이 전환이 없으면 한국어, 이모지, 코드 식별자, 새 제품명처럼 계속 생기는 문자열을 안정적으로 다루기 어렵다.

임베딩 계보 — 정적 단일 vector → contextual encoder → contrastive sentence encoder

섹션 제목: “임베딩 계보 — 정적 단일 vector → contextual encoder → contrastive sentence encoder”
  • word2vec/GloVe (2013~2014)의 한계: 한 단어는 하나의 vector만 갖는다. 그래서 “bank”가 은행인지 강둑인지 문맥별로 달라져도 같은 좌표를 쓴다. 이런 polysemy(다의어) 문제는 의미가 다른 문장들이 비슷한 거리로 나오는 silent failure를 만든다 (Stanford SAIL — How Contextual are Contextualized Word Representations?).
  • Contextual embedding (BERT 2018, transformer encoder): 각 token vector가 self-attention을 지나며 주변 문맥에 맞게 달라진다. “bank”라도 “deposit money” 옆에 있을 때와 “river bank” 옆에 있을 때의 layer 출력이 달라진다.
  • Contrastive sentence embedding (SimCSE/E5/BGE/voyage, 2021~2025): contextual encoder 위에 InfoNCE 같은 contrastive loss를 얹어 문장 단위 vector를 학습한다. 검색에서는 token 하나보다 query 문장과 document chunk 전체를 하나의 vector로 비교해야 하므로 이 계열이 중요하다.
  • L11-10 (ML 수학 토대): 벡터, norm, dot product, cosine similarity가 §4 의미 거리의 토대다. 이 정의가 없으면 MRL(Matryoshka Representation Learning, 앞쪽 차원만 잘라도 쓸 수 있게 학습하는 방식)·quantization이 “왜 차원을 줄여도 의미가 어느 정도 보존되나”를 설명할 수 없다.
  • L11-50 (transformer & attention): self-attention은 polysemy를 푸는 핵심 메커니즘이다. 이 토픽의 contextual embedding과 sentence embedding은 대부분 transformer encoder 위에 pooling·contrastive head를 얹은 구조로 이해하면 된다.

이 토픽이 비면 LLM API 비용 견적, context window 실효 활용, RAG 검색 품질, 다국어 처리 결정의 토대가 같이 빈다. 한국어 토큰 비용 2~3× 차이도 byte-level BPE 계보를 모르면 “왜?”에 답하기 어렵다.

3. 토크나이저 — 텍스트를 모델의 입력 단위로 바꾸기

섹션 제목: “3. 토크나이저 — 텍스트를 모델의 입력 단위로 바꾸기”

토크나이저 문서를 읽을 때 가장 먼저 헷갈리는 지점은 “단어, 글자, byte, subword가 모두 token이 될 수 있다”는 점이다.

  • token: 모델이 한 번에 ID로 읽는 입력 단위다. 영어 단어 하나일 수도 있고, 한국어 음절 일부일 수도 있고, 공백이 붙은 문자열일 수도 있다.
  • vocab: token 문자열과 token ID의 사전이다. vocab이 커지면 자주 나오는 긴 조각을 한 token으로 담을 수 있지만, embedding table과 tokenizer 파일도 커진다.
  • OOV(out-of-vocabulary): vocab에 없는 문자열이다. word-level에서는 [UNK]로 떨어질 수 있고, subword·byte-level 계열에서는 여러 조각으로 분해해 표현한다.
  • byte-level: unicode 문자를 바로 vocab에 넣지 않고 UTF-8 byte로 내려서 처리하는 방식이다. 한국어 한 글자는 보통 UTF-8 3 bytes라 영어보다 잘게 쪼개지기 쉽다.
  • special token: <|im_start|>, <|endoftext|>처럼 일반 사용자 텍스트가 아니라 모델 제어·chat template 경계를 나타내는 token이다. raw user input에 섞이면 prompt injection이나 출력 혼란으로 이어질 수 있다.

§2.5의 word-level → BPE → byte-level BPE 계보를 구현 단위로 펼치면 다음과 같다. BPE, SentencePiece, WordPiece는 모두 “단어를 더 작은 조각으로 나누되 너무 잘게 쪼개지는 비용은 줄이자”는 subword 계열이다.

3.2 토크나이저의 종류 비교

Word-level

예시: 옛 NLP

단어 하나를 token으로 둔다. 어휘 폭증과 OOV 문제가 크다

Character-level

예시: 글자 단위 모델

OOV는 줄지만 sequence가 길어진다. 긴 입력 비용이 커진다

BPE

예시: GPT, LLaMA, Mistral

빈도 높은 쌍을 반복 합병한다. vocab 크기와 sequence 길이의 균형점

SentencePiece

예시: T5, ALBERT, mT5

언어별 공백 규칙에 덜 의존하는 tokenizer 학습 도구. BPE와 Unigram을 지원

WordPiece

예시: BERT, DistilBERT

likelihood 기반 subword 선택. BERT 계열에서 `##` 접두로 이어지는 조각을 표시

tiktoken

예시: OpenAI GPT-3.5/4/o

OpenAI의 빠른 BPE 구현. byte-level BPE와 regex pre-tokenization을 사용

현대 LLM 표준은 byte-level BPE에 가깝다. 모든 unicode를 처리할 수 있고 OOV가 없다는 장점 때문이다. 대신 어떤 언어가 학습 코퍼스와 vocab에서 덜 잘 합병되었는지에 따라 token 수 차이가 커진다.

3.3 BPE 알고리즘 — 작은 합병이 vocab을 만든다

섹션 제목: “3.3 BPE 알고리즘 — 작은 합병이 vocab을 만든다”

BPE(Byte Pair Encoding)는 이름 그대로 자주 붙어 나오는 쌍을 합쳐 새 token을 만든다. 실제 구현은 regex pre-tokenization, byte fallback, special token 처리 등 세부가 있지만, 학습 원리는 다음 네 단계로 잡으면 된다.

1. 초기 어휘 = 모든 기본 단위(byte 또는 문자)
2. 코퍼스에서 가장 자주 붙어 나오는 쌍 (a, b)를 찾는다
3. 새 token ab를 vocab에 추가하고, 코퍼스의 (a, b)를 ab로 치환한다
4. 목표 vocab 크기(예: 32k, 100k, 200k)에 도달할 때까지 반복한다

손으로 보는 단순 예시:

코퍼스 조각:
low
lower
lowest
초기 표현:
l o w
l o w e r
l o w e s t
자주 붙는 쌍:
(l, o), (o, w)가 여러 번 등장
1차 합병:
l + o -> lo
lo w
lo w e r
lo w e s t
2차 합병:
lo + w -> low
low
low e r
low e s t

이제 lowest를 모르는 단어로 만나도 [UNK]가 아니라 low, e, s, t 같은 조합으로 표현할 수 있다. 학습이 더 진행되면 er, est, lowest 같은 token도 생길 수 있다. 어떤 조각이 최종 token이 되는지는 “언어학적으로 예쁜 단위”가 아니라 코퍼스 빈도와 vocab 예산에 의해 결정된다.

Llama 3 토크나이저는 128k vocabulary를 사용한다(Llama 2의 32k에서 4× 확대). vocab이 커지면 더 긴 조각을 한 token으로 담을 수 있어 token 효율이 좋아질 수 있지만, 모든 언어와 도메인에서 같은 폭으로 좋아지지는 않는다.

토큰 수는 LLM API 비용이고, context window의 실효 길이다. 같은 글이라도 어떤 tokenizer를 쓰느냐에 따라 비용이 달라진다.

언어모델영어 대비 비효율비고
한국어GPT-4 (cl100k_base)2.5~3ד안녕하세요, 세계!” ≈ 10~13 토큰
한국어GPT-4o (o200k_base)1.5~2×다국어 vocab 200k로 2× 확대 → 한국어 개선
한국어Llama 3 (tiktoken)1.5Llama 2 대비 평균 ~15% 토큰 절감
일본어GPT-4~2×
중국어GPT-4~1.5×한 글자 정보 밀도 ↑

한국어가 비싸지는 흐름은 이렇게 읽으면 된다.

  • 한국어 한 글자는 보통 UTF-8 3 bytes다. byte-level BPE에서 충분히 긴 한국어 조각이 vocab에 없으면 더 많은 token으로 쪼개진다.
  • vocab 크기가 작거나 학습 코퍼스가 영어 중심이면 한국어 합병이 덜 일어나 token 수가 늘어난다.
  • cl100k에서 o200k로 vocab이 커지면 한국어·일본어·중국어 같은 다국어 token 효율이 개선될 수 있다.
  • 같은 8k context window라도 한국어 문서는 모델에 따라 영어보다 훨씬 짧은 실효 본문만 들어갈 수 있다.

직접 측정해 보자. 이 예제는 token 효율을 감으로 말하지 않고 숫자로 보는 최소 실습이다 (openai/tiktoken).

# pip install tiktoken
import tiktoken
ko = "안녕하세요, 세계!"
en = "Hello, world!"
for name in ("cl100k_base", "o200k_base"): # GPT-4 / GPT-4o 계열
enc = tiktoken.get_encoding(name)
ko_tok = len(enc.encode(ko))
en_tok = len(enc.encode(en))
print(f"[{name:13}] KO={ko_tok:2}t EN={en_tok}t ratio={ko_tok/en_tok:.2f}x")

예상 출력:

[cl100k_base ] KO=11t EN=4t ratio=2.75x ← GPT-4: 한국어 ~2.75배 비효율
[o200k_base ] KO= 7t EN=4t ratio=1.75x ← GPT-4o: vocab 200k로 개선

결과가 예상과 다르면 먼저 세 가지를 본다. prompt 끝의 trailing space가 붙었는지, special token이 포함됐는지, 텍스트에 한자·이모지·복잡 unicode가 많은지 확인한다. token 수 차이는 작은 문자열에서도 바로 보이지만, 실제 청구 토큰과는 chat template·system message·tool call wrapper 때문에 1 이상 차이날 수 있다.

토크나이저 문제는 컴파일 에러처럼 터지지 않고, 비용 증가나 품질 저하로 조용히 나타난다.

실패 신호왜 생기는가먼저 볼 것
"hello"" hello" 결과가 다름공백까지 token의 일부가 될 수 있다prompt 조립 시 앞뒤 공백, 줄바꿈
응답에 <|im_start|>가 보임special token이 일반 텍스트처럼 섞였다user input sanitize, chat template 경계
fine-tune 후 추론이 깨짐tokenizer mismatch. 학습·추론 tokenizer/template이 다름base 모델 tokenizer와 chat template 일관성
숫자 계산이 이상함"1000""1001"이 다른 조각으로 쪼개질 수 있다숫자 tokenization, 계산은 tool 또는 코드로 위임
한국어·이모지 비용이 튐byte-level fallback으로 token 수가 늘었다tokenizer별 실제 token 수 측정

tokenizer mismatch는 특히 위험하다. fine-tuning 데이터는 A tokenizer로 잘 학습된 것처럼 보이는데, 추론에서 B tokenizer나 다른 chat template을 쓰면 token ID의 의미가 달라진다. 모델 입장에서는 같은 문자열이 다른 입력 좌표로 들어오는 셈이라, loss는 내려갔는데 실제 응답은 깨지는 silent failure가 생길 수 있다.

4. 임베딩 — 의미를 벡터 공간에 놓기

섹션 제목: “4. 임베딩 — 의미를 벡터 공간에 놓기”

임베딩은 대상을 숫자 벡터로 바꾸는 함수다.

"고양이" -> [0.21, -0.18, 0.55, ...]
"강아지" -> [0.19, -0.20, 0.58, ...]
"비행기" -> [-0.42, 0.71, 0.03, ...]

중요한 점은 각 차원이 사람이 붙인 “귀여움”, “탈것” 같은 명시적 의미를 갖지 않는다는 것이다. 보통은 사람이 해석하기 어려운 수백~수천 개 숫자 좌표이고, 학습 목표는 비슷한 의미가 가까운 위치에 오도록 만드는 것이다.

  • Token embedding: token ID를 vector로 바꾸는 LLM 내부의 첫 표현이다. word2vec의 정적 단일 vector를 self-attention으로 문맥별 vector로 일반화한 산물이다.
  • Contextual token embedding: transformer layer를 지난 token 표현이다. 같은 token “bank”라도 문맥에 따라 좌표가 달라진다.
  • Sentence embedding: 문장 또는 문서 chunk 전체를 단일 vector로 바꾼다. RAG·검색·분류에서 주로 쓴다.
  • Image embedding: 이미지를 vector로 바꾼다. CLIP 계열이 대표적이다.
  • Cross-modal embedding: 텍스트와 이미지를 같은 공간에 놓는다. 텍스트 query로 이미지를 찾거나, 이미지와 설명을 비교할 수 있다.

검색에서 주로 쓰는 것은 sentence embedding이다. token embedding은 LLM 내부 계산의 재료이고, sentence embedding은 외부 검색 시스템에서 “이 query와 이 문서가 가까운가”를 빠르게 비교하는 도구다.

좋은 임베딩 공간은 세 성질을 기대한다.

  • 의미가 거리로 나타난다: 비슷한 의미의 문장·문서가 가까운 vector가 된다.
  • 군집이 생긴다: 같은 토픽의 문서가 비슷한 방향에 모인다.
  • 일부 연산 직관이 생긴다: king - man + woman ≈ queen 같은 word2vec 예시는 역사적으로 유명하다. 현대 sentence embedding에서는 이 직관을 과하게 일반화하면 안 되지만, “좌표 차이가 의미 차이를 일부 담는다”는 출발점으로는 유용하다.

경계도 있다. **anisotropy(비등방성)**는 임베딩이 넓게 퍼지지 않고 좁은 cone에 몰리는 현상이다. 이때는 모든 cosine similarity가 비슷하게 높아져 거리 차이가 의미 차이를 잘 구분하지 못한다. contrastive learning은 positive를 가깝게, negative를 멀게 밀어 이런 쏠림을 완화하려는 학습 압력을 만든다.

4.3 의미 거리 — cosine, dot, Euclidean

섹션 제목: “4.3 의미 거리 — cosine, dot, Euclidean”

L11-10에서 본 벡터 수학을 검색 언어로 바꾸면 다음 표가 된다.

거리사용
Cosine similarity(a · b) / (||a|| ||b||)방향만 — RAG·검색 표준
Euclidean||a - b||_2위치와 크기 — 임베딩 검색에서는 덜 흔함
Dot producta · b정규화된 임베딩에서는 cosine과 같음
Inner productdot product의 일반 표현FAISS·pgvector 인덱스 옵션

작은 숫자로 확인해 보자.

a = [3, 4]
b = [6, 8]
a · b = 3*6 + 4*8 = 50
||a|| = 5
||b|| = 10
cos(a, b) = 50 / (5*10) = 1

ba를 2배 늘린 벡터라 방향이 완전히 같다. cosine은 방향만 보므로 1이다. 반대로 길이는 같아도 방향이 다르면 비슷하다고 볼 수 없다.

a = [3, 4]
c = [-4, 3]
a · c = 3*(-4) + 4*3 = 0
||a|| = 5
||c|| = 5
cos(a, c) = 0 / (5*5) = 0

이 반례가 검색에서 중요하다. 문서 vector의 크기가 크다고 query와 관련 있는 것이 아니다. 보통 LLM 임베딩 모델은 L2 정규화된 vector를 내보내거나 정규화를 권장한다. 정규화된 vector에서는 ||a|| = ||b|| = 1이므로 cosine과 dot product가 같아지고, dot product 검색이 계산상 더 빠르다.

4.4 임베딩 학습 — Contrastive Learning

섹션 제목: “4.4 임베딩 학습 — Contrastive Learning”

sentence embedding은 보통 “같은 의미는 가깝게, 다른 의미는 멀게” 학습한다. 이를 **contrastive learning(대조 학습)**이라고 한다.

Loss(InfoNCE) = -log[exp(sim(a, p)/τ) / Σ_n exp(sim(a, n)/τ)]
a: anchor (query 또는 문서)
p: positive (a와 의미가 같은 것)
n: negative (의미가 다른 것)
τ: temperature

식의 직관은 단순하다. anchor a와 positive p의 similarity는 키우고, negative n들과의 similarity는 줄인다. τ는 softmax의 날카로움을 조절하는 온도다.

예를 들어 query가 “비밀번호 재설정 방법”이라면 positive는 같은 문제를 다룬 문서다. easy negative는 “휴가 신청 방법”처럼 표면도 의미도 다른 문서이고, hard negative는 “비밀번호 정책 변경 공지”처럼 단어는 비슷하지만 답은 아닌 문서다. hard negative를 잘 넣어야 모델이 단어 겹침이 아니라 의미와 의도를 구분한다.

SimCSE, GTR, E5, BGE는 모두 이 흐름의 변형이다. positive 쌍은 번역 대응 문장, paraphrase, query-document 클릭 로그, instruction-response pair 등에서 만들 수 있다.

아래 표는 문서 작성 시점의 출처와 벤치마크를 근거로 한 선택지 요약이다. 최신 리더보드 순위가 목적이 아니라, 어떤 축을 보고 고르는지 읽는 것이 핵심이다.

MTEB(Massive Text Embedding Benchmark) 는 분류·검색·유사도 등 여러 임베딩 과업을 묶어 비교하는 벤치마크다. 아래 점수는 모델 카드와 공개 결과의 선택 참고값이며, 서로 다른 버전·과업 subset·언어 구성을 섞어 절대 순위로 읽지 않는다.

모델차원ContextMTEB특징
OpenAI text-embedding-3-small15368k62.3가성비. Matryoshka. $0.02/1M
OpenAI text-embedding-3-large30728k64.6OpenAI 생태계 default. Matryoshka. $0.13/1M
Cohere embed-v4 (2025)up to 1536128k-다국어, 멀티모달, MRL
Voyage AI voyage-4-large256~204832k자체 검증Voyage가 현재 general-purpose·multilingual 최고 품질 후보로 안내. MRL + int8/binary
BGE-M3 (2024-01)10248k~63공식 dense 출력은 1024d. dense+sparse+ColBERT 모드
NV-Embed-v2 (NVIDIA)409632k72.3공식 카드 기준 Mistral-7B-v0.1 기반, decoder-as-encoder
Qwen3-Embedding-8B32~4096 (flex)32k70.6flexible dim, 다국어 강함
Stella / GritLM / mxbai-embed다양다양60~67open-weight 경량
Gemini Embedding 2 (Google)768~30728k+65+Google 진영

2025년 전후 공개 벤치 기준으로는 open-weight + decoder-as-encoder 계열(E5-mistral, NV-Embed, GritLM)이 강하게 올라왔다. 이 순위는 시간이 지나면 바뀔 수 있으므로, 표는 “현재 1등 외우기”가 아니라 모델을 고를 때 볼 축을 익히는 용도로 읽는다. OpenAI 3-large는 “절대 최고 품질”이라기보다 OpenAI API 생태계 안에서 기본 선택지로 보기 쉽다.

선택 기준은 네 축으로 압축된다.

  • 비용·운영 단순성: OpenAI 생태계라면 text-embedding-3-small(비용) 또는 3-large(품질)를 먼저 비교한다.
  • 다국어 품질: 현재 API 후보인 voyage-4-large, open-weight MRL 후보인 Qwen3-Embedding, 1024d multi-mode 후보인 BGE-M3를 같은 자체 평가셋에서 검토한다.
  • on-prem·privacy: BGE-M3, NV-Embed-v2 같은 open-weight 모델이 후보가 된다.
  • 한국어 특화: BGE-M3 기반 fine-tune 모델, KoSimCSE, ko-sroberta를 자체 검증셋으로 비교한다.

차원과 dtype은 검색 품질뿐 아니라 storage와 메모리 비용을 직접 바꾼다. L11-10의 저장 공간 감각을 그대로 쓰면 된다.

저장 공간 대략 = 벡터 개수 * 차원 수 * 숫자 1개의 바이트

예를 들어 1,000,000개 문서를 1024차원 fp32로 저장하면:

1,000,000 * 1024 * 4 bytes
= 4,096,000,000 bytes
≈ 4.1 GB

여기서 네 가지 압축 계열을 구분해야 한다.

  • MRL(Matryoshka Representation Learning): 학습할 때부터 앞쪽 256d, 512d, 1024d 같은 prefix만 잘라도 의미가 남도록 훈련한다. 사후 압축이 아니라 “절단 가능한 순서”를 학습한다.
  • PCA(Principal Component Analysis): 이미 만든 vector를 사후에 중요한 방향으로 투영해 차원을 줄인다. 모델이 그 절단을 전제로 학습한 것은 아니라 손실이 커질 수 있다.
  • PQ(Product Quantization): vector를 여러 chunk로 나누고 chunk별 codebook ID로 압축한다. FAISS에서 많이 쓰는 ANN 압축 방식이다.
  • Quantization: fp32 숫자를 int8 또는 binary처럼 더 적은 bit로 저장한다. 차원 수는 같아도 숫자 하나의 byte가 줄어든다.

Matryoshka worked example:

일반 임베딩: 1536차원 vector를 그대로 사용
Matryoshka: 256, 512, 768, 1024, 1536 절단점에서도 검색 품질을 유지하도록 학습

OpenAI text-embedding-3-largedimensions 파라미터로 256~3072 차원을 선택할 수 있다. OpenAI 공식 공지 기준으로 3-large 풀(3072d) MTEB 64.6이고, 256d로 절단해도 ada-002(1536d, MTEB 61.0)를 상회한다 (OpenAI 공식 공지).

모델 이름과 차원 축소 기능은 분리해서 읽어야 한다. BGE-M3 공식 모델 카드는 dense embedding을 1024d로 명시하고 dense·sparse·multi-vector 기능을 설명하지만, vanilla BAAI/bge-m3가 앞 256차원을 잘라 쓰도록 Matryoshka 학습됐다고 광고하지 않는다. 반면 Voyage 공식 문서는 voyage-4-large가 1024d를 기본으로 256/512/2048d 출력을 지원하고, 앞쪽 차원을 절단해 쓰는 Matryoshka embedding이라고 명시한다. Qwen3-Embedding 모델 카드도 MRL 지원과 custom dimension을 명시한다. 따라서 BGE-M3 1024dMRL 지원 모델의 256d는 서로 다른 후보이며, BGE-M3를 256d로 단순 slice하는 실험을 native 지원처럼 쓰지 않는다 (BGE-M3 공식 모델 카드, Voyage flexible dimensions).

quantization 수치 감각:

압축 방식대략 효과경계
fp32 → int8메모리 4× 절감검색 품질 거의 100% 보존 보고가 많지만 실측 필요
fp32 → binary메모리 32× 절감, 속도 ~40× ↑fp32 rescore 전후 recall·latency 곡선으로 2단계 여부 결정
1536d → 256d + binary원본 대비 storage 192× 절감 가능MRL 지원 모델일 때 의미 있는 조합

숫자로 보면 더 선명하다. 1M 문서, 1024d fp32는 약 4.1GB다. 256d로 줄이면 차원 4× 절감으로 약 1.0GB가 된다. 여기에 binary를 쓰면 숫자 하나가 32bit에서 1bit로 줄어 32MB 수준이 된다. 1024d 원본과 비교하면 128×, 1536d 원본과 비교하면 192× 절감 감각이다.

경계는 명확하다. MRL을 지원하지 않는 모델에서 앞 256차원만 잘라 쓰면 의미 보존을 기대할 근거가 약하다. 작은 모델이나 이미 짧은 차원에서는 int8 효과도 작을 수 있다. 압축은 항상 recall@10, nDCG@10, P95 latency를 같이 보고 결정한다.

5. 임베딩 검색 — dense만으로 끝나지 않는다

섹션 제목: “5. 임베딩 검색 — dense만으로 끝나지 않는다”

Dense embedding은 대부분의 차원이 0이 아닌 일반적인 실수 vector다. 자연어 의미 검색에는 강하지만, 다음 영역에서는 약해질 수 있다.

  • 고유명사: 사내 프로젝트명, 제품명, 사람 이름
  • 법률·규정: 조항 번호, 정확한 문구
  • 코드·로그: 함수명, 에러 코드, 파일 경로
  • 신조어·약어: 학습 시점이나 도메인 밖 표현

이 영역에서는 “의미가 비슷한 문서”보다 “정확한 문자열이 들어 있는 문서”가 먼저 필요할 수 있다. 그래서 lexical search와 dense search를 섞는다.

섹션 제목: “5.2 BM25, sparse embedding, ColBERT, hybrid search”

첫 등장 용어를 먼저 정리한다.

  • BM25: 단어가 query와 얼마나 잘 겹치는지, 문서 길이와 단어 희소성을 고려해 점수화하는 lexical ranking 방식이다. 정확한 키워드·고유명사에 강하다.
  • Sparse embedding: 대부분의 차원이 0이고 일부 어휘 차원에만 가중치가 있는 vector다. SPLADE처럼 학습으로 query와 문서의 어휘 가중치를 확장할 수 있다.
  • ColBERT: 문서 전체를 한 vector로 압축하지 않고 token별 vector를 여러 개 유지한 뒤 late interaction으로 query token과 문서 token을 비교한다. 세부 단서 보존에 강하지만 저장량과 계산량이 늘어난다.
  • Hybrid search: BM25 또는 sparse 결과와 dense 결과를 함께 쓰는 검색 방식이다.
  • RRF(Reciprocal Rank Fusion): 여러 ranking의 점수를 1 / (k + rank) 형태로 합쳐 순위를 결합하는 방법이다. 절대 점수 스케일이 달라도 rank만으로 섞을 수 있다.

RRF를 작은 예시로 보자. k=60이고 query가 “SAML SSO ADFS 0x800…”처럼 고유명사와 에러 코드가 중요한 경우다.

문서dense rankBM25 rankRRF 점수 개념
A: 일반 로그인 가이드1201/61 + 1/80
B: ADFS 에러 코드 문서611/66 + 1/61
C: SSO 용어집1221/72 + 1/62

dense만 보면 A가 1등이지만, BM25는 exact match가 있는 B를 1등으로 올린다. RRF를 적용하면 B가 dense에서도 후보권에 있고 BM25에서는 최상위라 최종 순위가 올라간다. 이것이 hybrid search의 핵심이다. 의미 검색과 정확 매칭 중 하나를 고르는 것이 아니라, 서로 다른 실패 모드를 보완한다.

BGE-M3는 dense + sparse + ColBERT(multi-vector late interaction)를 한 모델에서 출력할 수 있다. 셋을 RRF로 결합하면 단일 dense 대비 큰 개선을 기대할 수 있다. 한국어 운영에서는 고유명사·법령·약어 비중이 크면 BM25/sparse 가중치를 dense보다 높이는 선택이 흔하다.

Reranker는 1차 검색 결과 후보를 더 비싼 모델로 다시 점수화해 순서를 바꾸는 2단계 검색 도구다.

Stage 1 (retrieve): bi-encoder 임베딩으로 top-100 후보 검색 (빠름)
Stage 2 (rerank): cross-encoder reranker로 top-100을 top-10으로 재정렬 (정확)
  • bi-encoder: query와 document를 따로 vector로 만들어 미리 색인할 수 있다. 빠르지만 query-document 상호작용을 한 vector 거리로 압축한다.
  • cross-encoder: query와 document를 같이 입력해 직접 관련도를 점수화한다. 정확하지만 후보마다 forward가 필요하므로 top-100 같은 좁은 후보에만 적용한다.

운영 표준 reranker:

  • BGE-reranker-v2-m3 (open, 다국어)
  • rerank-2.5 / rerank-2.5-lite (Voyage API의 현재 권장 후보)
  • Cohere Rerank 3.5 (API)

reranker는 한국어 RAG에서 ROI가 큰 도구다. recall@10이 보통 1030% 개선될 수 있다. 단, top-100 안에 정답이 들어오지 않는다면 reranker는 없는 답을 만들 수 없다. 먼저 recall@100을 확인하고, 그다음 top-100 → top-10 재정렬 비용(보통 50100ms 추가)을 감당할지 결정한다.

문서가 수십만~수백만 개가 되면 모든 vector와 query vector를 매번 비교하기 어렵다. 그래서 ANN(Approximate Nearest Neighbor, 근사 최근접 이웃) 검색을 쓴다. 정확한 top-k를 완전탐색하지 않고, 가까울 가능성이 높은 후보를 빠르게 찾는다.

**HNSW(Hierarchical Navigable Small World)**는 ANN index의 대표 방식이다. 여러 층의 그래프를 만들고, query vector가 가까운 이웃을 따라가며 후보를 찾는다. DB의 B-tree가 정렬된 key를 빠르게 찾는다면, HNSW는 벡터 공간에서 가까운 점을 그래프 탐색으로 찾는다고 보면 된다.

경계도 있다. HNSW는 ef_search, M 같은 index 설정에 따라 recall과 latency가 trade-off된다. 하지만 처음 읽을 때는 knob를 외우기보다 “근사 검색은 빠른 대신 recall 손실이 있을 수 있고, reranker는 후보 안에서만 고칠 수 있다”는 실패 원리를 잡는 것이 더 중요하다.

6.1 자체 실험을 시작하기 위한 가상 관찰 표

섹션 제목: “6.1 자체 실험을 시작하기 위한 가상 관찰 표”

아래 표는 “무엇이 좋은가”보다 “어떤 조건에서 깨지는가”를 보는 표다. 표의 숫자는 특정 모델·corpus·hardware에서 나올 수 있는 가상 관찰 예이며, 바로 적용할 정책이나 보편 임계값이 아니다. 자체 검증셋의 sweep 범위를 정하는 출발점으로만 쓴다.

기법효과 발휘 범위깨지는 조건
Dense embedding only일반 자연어 QA고유명사·법률·코드·신조어 多 → BM25 가중치 ↑
BM25 only키워드 정확 매칭paraphrase·의미 검색 → dense 보강 필요
Hybrid (RRF k=60)일반 도메인고유명사 압도 도메인 → α 0.4:0.6 (BM25 우위)
Rerankertop-100 → top-10 재정렬top-10 이미 정확하면 50~100ms 추가 손해
MRL 지원 모델 256d가상 관찰에서 quality 1~3% 손실더 짧은 차원의 손실 곡선은 모델·corpus별 재측정
int8 quantization4× storage ↓, 품질 ~100%작은 모델·짧은 차원에선 효과 미미
Binary embedding32× storage·40× 속도단독·fp32 rescore 후보를 같은 recall·latency 예산에서 비교
KoSimCSE·BGE-M3-ko한국어 전용 도메인다국어 cross-lingual 검색엔 다국어 모델 우위

여기서 α 0.4:0.6은 dense 점수와 BM25 점수를 섞는 가중치 예시다. 고유명사·법령 번호·코드 식별자처럼 정확한 문자열 매칭이 중요하면 BM25 쪽 비중을 높이고, paraphrase가 많으면 dense 쪽 비중을 높인다.

읽는 순서는 이렇다.

  1. 검색이 틀리면 먼저 dense 모델 자체보다 query/document chunk와 평가셋을 본다.
  2. exact match가 중요한 실패라면 BM25 또는 sparse를 붙인다.
  3. top-100에는 정답이 있는데 top-10 순서가 나쁘다면 reranker를 붙인다.
  4. storage·latency가 문제라면 MRL, int8, binary, PQ를 검토한다.
  5. 압축 후 품질은 평균 점수만 보지 말고 한국어, 고유명사, 긴 문서, OOD 도메인 slice로 나눠 본다.

6.2 Silent Failure 시나리오와 대응 원리

섹션 제목: “6.2 Silent Failure 시나리오와 대응 원리”
증상정량 시그널원인대응 원리
한국어 query 검색 부정확recall@10 < 50%영어 임베딩 모델 사용다국어 모델(BGE-M3, Voyage 4 계열)로 비교
고유명사 검색 실패exact match 점수 0%dense만 사용hybrid (BM25 + dense + RRF)
활성 index에 model version 혼재미승인 model_version이 2개 이상보장 없는 공간 혼합 또는 호환 설정 불일치공식 호환 계약 확인, 없으면 새 index 전체 재임베딩
MRL 절단 후 품질 폭락recall@10 30%+ ↓MRL 미지원 모델MRL 지원 모델로만 절단 실험
Token 비용 폭증 (한국어)토큰/요청 영어 대비 3×+옛 토크나이저 사용 (cl100k)o200k 계열 모델과 비용 비교
Special token 누출응답에 <|im_start|> 포함sanitize 누락input/output filter와 template 경계 점검
Tokenizer mismatch (fine-tune)추론 결과 깨짐base와 다른 토크나이저 사용base 모델 tokenizer/template 일관 사용

이 표의 목적은 장애 대응 명령어를 외우는 것이 아니다. 증상과 원인을 연결해 “어떤 개념이 깨졌는가”를 먼저 좁히는 것이다. 예를 들어 고유명사 exact match가 0%라면 dense embedding의 의미 압축이 실패한 것이고, tokenizer mismatch는 모델 입력 ID 체계가 학습 때와 달라진 것이다.

6.3 모델 버전 변경과 dual-index migration

섹션 제목: “6.3 모델 버전 변경과 dual-index migration”

embedding 모델을 바꾸면 기본적으로 vector 좌표계도 바뀐다. 같은 문장이라도 ada-002와 3-small처럼 상호 호환이 보장되지 않은 모델의 vector는 서로 다른 공간에 놓인다. 차원이 우연히 같아도 각 축과 방향의 의미가 같다는 보장은 없으므로, 옛 vector와 새 vector를 한 index에 섞어 거리 비교를 해서는 안 된다. 두 모델이 같은 문장을 만든 cosine 값이 높다는 사실도 호환성 증명이 아니다.

예외는 벤더가 특정 모델군의 cross-model embedding compatibility를 명시적으로 보장한 경우다. 현재 Voyage 공식 문서는 voyage-4-large, voyage-4, voyage-4-lite, voyage-4-nano가 만드는 Voyage 4 계열 임베딩이 서로 호환된다고 밝힌다. 이 계약은 임의의 다른 Voyage 모델이나 다른 벤더 모델로 확장할 수 없다. 또한 호환 모델군이어도 output_dimension, output_dtype와 quantization 표현, 정규화, distance metric, query/document 입력 계약이 기존 index schema와 맞는지 확인해야 한다. 차원이 다르면 직접 거리 계산을 할 수 없고, binary와 float처럼 저장 표현이 다르면 같은 index 연산 경로를 그대로 쓸 수 있다고 가정해서는 안 된다.

OpenAI deprecations 문서는 오래된 embedding 모델의 retire 일정과 대체 모델을 공지한다 (OpenAI deprecations). 특정 모델 교체 일정과 호환 보장은 공식 공지에서 각각 다시 확인한다. 명시적 호환 보장이 없으면 모델 A query는 A 문서 index에서, 모델 B query는 B로 전체 재임베딩한 별도 index에서 검색한다. 명시적 보장이 있더라도 같은 gold query·relevance label로 recall@k, nDCG@k, latency, 비용과 실패 slice를 비교한 뒤 cutover한다. 모델 A/B의 같은 문장 vector를 cosine threshold로 비교하는 검사는 어느 경우에도 벤더 호환 계약이나 retrieval 평가를 대신하지 못한다.

학습 관점에서 안전한 절차는 다음 순서다.

  1. compatibility gate: 공식 모델 카드·API 문서에서 source/target 모델 사이의 cross-model 호환 보장과 지원 차원·dtype을 확인해 증거 링크와 설정을 migration record에 남긴다. 문서에 명시되지 않은 호환성은 없는 것으로 취급한다.
  2. separate index + dual-write: 보장이 없거나 dimension·quantization·index schema가 달라지면 새 index를 만들고 새 문서를 옛 모델·index와 새 모델·index에 각각 기록한다. production traffic은 아직 옛 경로만 본다. 호환 보장이 없는 vector를 같은 index에 넣지 않는다.
  3. conditional backfill: 보장이 없으면 기존 대상 corpus 전체를 새 모델로 batch re-embedding한다. 90%는 진행 이정표일 수 있지만 cutover 조건이 아니다. 예상 문서·chunk가 100% 채워졌거나 제외 사유가 명시돼야 한다. Voyage 4 계열처럼 보장이 있으면 full re-embedding을 생략할 후보가 될 수 있지만, 동일한 지원 차원·표현과 index 계약을 확인한 경우에만 그렇다.
  4. shadow retrieval: 보장이 없으면 같은 원문 query를 모델별로 따로 임베딩해 각각 대응 index에 보낸다. 보장이 있으면 새 모델 query로 기존 호환 index를 조회하는 경로도 별도 실험군으로 둔다. 어느 쪽이든 같은 gold relevance 기준으로 recall@10, nDCG@10, latency, 비용과 실패 slice를 비교하며, cross-model cosine threshold를 승인 기준으로 쓰지 않는다.
  5. traffic switch: 새 경로의 schema·dimension·quantization 검증, 필요한 corpus 완전성, retrieval 평가가 승인 기준을 만족할 때 alias·routing을 100% cutover하고, 1~2주 옛 index를 read-only로 보존한다.
  6. purge: 사용자 캐시·로그에 남은 옛 vector 참조를 만료시킨 뒤 옛 index를 삭제한다.

호환 보장이 없거나 dimension·quantization 계약이 달라지는데 dual-index 없이 in-place로 reindex하면 진행 중 query 결과가 “옛 vector vs 새 vector”가 섞인다. 이때 나쁜 결과가 모델 문제인지, index 혼합 문제인지, backfill 누락인지 구분하기 어렵다. dimension이 다른 모델 간 마이그레이션은 더 까다롭다. ada-002(1536d)와 3-large(3072d)는 storage·index schema 자체가 다르다.

임베딩의 핵심은 “고차원 공간에 의미 좌표를 만들고, 거리로 유사도를 비교한다”이다. 이 패턴은 다른 시스템에서도 반복된다.

임베딩 구성요소일반 시스템 매핑
Vector (의미 좌표)DB row, document, user profile, hash code
Distance (cosine, Euclidean)DB join condition, similarity score
ANN index (HNSW)DB index (B-tree, LSM-tree), cache key index
Quantization (int8, binary)columnar compression, bloom filter
Matryoshka (사전 절단)hierarchical index, summary-detail trade-off
Hybrid (BM25 + dense)full-text + structured query (Postgres + tsvector)
Reranker (cross-encoder)2-stage cache lookup, allowlist + verification

일반 공식은 근사 검색 + 정확 재정렬이다. 먼저 ANN/HNSW나 BM25로 후보를 빠르게 좁히고, 그다음 reranker나 더 정확한 scoring으로 최종 순서를 정한다. 임베딩은 데이터를 의미 공간으로 옮기는 도구이고, 그 위의 검색은 거리·랭킹·재정렬의 조합이다.

새 검색·임베딩 기술을 만나면 다음 5질문으로 분해한다.

  1. 표현 단위·dtype: vector 차원·정규화 여부·fp32/int8/binary 중 무엇인가? storage와 index 선택을 결정한다.
  2. 거리 정의: cosine, dot, Euclidean, RRF 중 무엇인가? 정규화 필요 여부와 후처리 비용이 달라진다.
  3. 2-stage 가능 여부: bi-encoder retrieve + cross-encoder rerank가 가능한가? top-100 안에 정답이 들어오는지 먼저 봐야 한다.
  4. 압축의 native 지원: MRL·quantization을 모델 학습 단계에서 지원하나, 사후 PCA를 강제하나? native면 손실 15%, 사후만 가능하면 1030% 손실 가능성이 있다.
  5. Silent failure 측정 지표: 모델 교체에는 recall@10·nDCG@10·exact match 같은 retrieval regression을, 같은 모델·같은 pipeline의 재현성에는 vector norm·동일 입력 cosine 같은 진단값을 정했나? Cross-model 호환성은 cosine threshold가 아니라 명시적 벤더 계약으로 확인하며, 보장이 없으면 전체 재임베딩한다.

8. 운영 시나리오 — 한국어 RAG 임베딩 모델 결정

섹션 제목: “8. 운영 시나리오 — 한국어 RAG 임베딩 모델 결정”

아래 시나리오는 §7의 5질문을 실제 의사결정에 적용한 예시다.

상황:
사내 한국어 문서 1M개
검색 P95 < 100ms
recall@10 > 0.7
5질문 적용:
Q1 단위·dtype : BGE-M3 후보는 공식 1024d로 유지
256d × binary가 필요하면 Voyage 4처럼 해당 출력을 명시한 별도 모델 검토
Q2 거리 정의 : dense cosine + sparse + ColBERT를 RRF로 결합
Q3 2-stage : bi-encoder retrieve(top-100) + bge-reranker-v2-m3 cross-encoder
Q4 압축 native: BGE-M3는 dense+sparse+multi-vector를 지원하지만 공식 차원은 1024d
차원 절단은 MRL 지원을 명시한 별도 모델에서만 실험
Q5 SF 지표 : recall@10 < 0.5 → hybrid 가중치 재조정
exact match 0% → BM25 가중치 ↑

선택지 비교:

API 임베딩 비용은 2026-07-14에 확인한 공식 가격의 예시 단가로, 본문 토큰을 중복 없이 한 account에서 1회 처리한다고 가정한다. 모델 입력 한도를 초과하는 문서는 overlap 없이 여러 요청으로 분할하되, 분할 전후의 총 과금 토큰은 본문 토큰 합계와 같다고 본다.

Voyage 공식 가격 페이지voyage-4-large$0.12 / 1M tokens로 안내하면서 현재 모델군의 account별 first 200M tokens 무료 할당을 명시한다. 이를 월별로 갱신되는 무료 tier라고 쓰지는 않으므로, 아래 계산은 월간 reset을 가정하지 않는다. 한 번의 초기 적재 전에 해당 account의 200M이 전부 남은 상태와, 이미 소진되어 모든 추가 token에 한계비용이 붙는 상태를 분리한다. 실제 계약에서는 무료 할당의 잔여량과 모델 간 공유 조건을 다시 확인한다.

무료 할당 소진 후 한계비용 = 총 토큰 / 1,000,000 * 1M tokens당 가격
Voyage 무료 200M 전부 잔여 = max(총 토큰 - 200M, 0) / 1,000,000 * $0.12
1M docs * 10k tokens/doc = 10B tokens
OpenAI 3-large: 10B / 1M * $0.13 = $1,300
Voyage, 무료 소진: 10B / 1M * $0.12 = $1,200
Voyage, 200M 잔여: (10B - 200M) / 1M * $0.12 = $1,176

평균 문서 길이에 따른 민감도는 다음과 같다.

평균 토큰/문서1M 문서의 총 토큰OpenAI $0.13/1MVoyage 한계비용
(무료 소진)
Voyage 초기 비용
(200M 전부 잔여)
1k1B$130$120(1B-200M)×$0.12/1M = $96
10k10B$1,300$1,200(10B-200M)×$0.12/1M = $1,176

이 계산은 세금·환율·프로모션 변경뿐 아니라 chunking 과정의 overlap, 중복 문서·중복 chunk, metadata나 prefix 토큰, 실패 재시도, 모델 변경에 따른 재임베딩 비용을 제외한다. 실제 ingestion 비용은 chunk별 실제 토큰 합계와 처리 시점의 account 잔여 무료 token으로 같은 식을 다시 계산해야 한다. 이는 한 번의 초기 적재 비용이며, 아래 self-host의 $200/월 가정과 기간이 다르므로 직접 손익분기점처럼 비교하지 않는다.

저장량은 위의 1M docs 토큰 비용과 별개로 1M vectors를 기준으로 계산하며, vector 수는 실제 chunk 수와 같다. 문서당 chunk가 2개면 1M docs가 2M vectors가 되어 raw fp32 저장량도 2배가 된다(3072d 약 12GB → 약 24GB, 1024d 약 4GB → 약 8GB). 이 값은 vector 원본만 계산하며 ANN index와 metadata overhead는 제외한다.

선택지장점비용·저장 감각경계
A. text-embedding-3-large (3072d, OpenAI)OpenAI 생태계, MatryoshkaAPI: $0.13/1M tokens, 1M docs ≈ $1300. 저장: 1M vectors fp32 약 12GB다국어 최고 품질 목적이면 별도 검증 필요
B. BGE-M3 (1024d, self-host)한국어 강함, dense+sparse+ColBERT서빙: GPU $200/월 가정. 저장: 1M vectors fp32 약 4GB운영 부담과 모델 서빙 필요
C. voyage-4-large (1024d default)현재 Voyage 다국어 API 후보, MRL + int8/binary10k tokens/doc 가정의 1M docs: 무료 소진 후 $1,200, account의 200M이 전부 남으면 $1,176. 저장: 1M vectors fp32 약 4GB무료 잔여량·API 가격 변동과 외부 의존
D. C의 256d + binary + 2-stage reranknative MRL·quantization으로 storage 실험저장 1M vectors: 1024d fp32 4GB → 256d binary 약 32MB(128×), 1536d 기준 192×자체 recall@10·P95 검증 없이는 위험

선택 예시:

가상 선택: D (voyage-4-large의 공식 지원 256d/binary + BM25 hybrid + 2-stage rerank)
비선택 이유:
A는 운영 단순성은 좋지만 한국어·다국어 품질과 비용을 자체 평가해야 함
B는 self-host와 multi-mode 장점이 있지만 vanilla BGE-M3는 공식 1024d이며 native 256d 절단 후보가 아님
C의 1024d fp32는 품질 기준선이며 storage와 latency를 더 줄일 여지가 있음

기대치와 책임 범위:

  • storage 32MB: voyage-4-large가 공식 지원하는 1024d→256d로 4×, fp32→binary로 32×를 곱한 raw payload 계산이다. 1536d fp32 원본과 비교하면 192× 절감 감각이지만 metadata·index·replica는 별도다.
  • 품질 retention: binary 단독 9098% recall, fp32 rescore 결합 시 ~99%라는 기존 수치는 Cohere 모델과 MIRACL(Multilingual Information Retrieval Across a Continuum of Languages, 다국어 검색 벤치마크) 조건의 보고다. Voyage 4나 BGE-M3의 보편 품질 수치로 옮기지 않는다. 각 후보에서 binary 단독과 fp32 rescore의 후보 폭을 sweep하고, 추가 recall이 latency·storage 예산을 정당화할 때만 2단계를 채택한다 (Cohere int8/binary 보고).
  • 다국어 retrieval: BGE-M3 dense+sparse+ColBERT RRF 결합 시 dense-only 대비 큰 폭 개선, MIRACL 18 언어 평균 nDCG@10 70.0 (BGE-M3 paper, arXiv:2402.03216).

이 수치는 외부 벤치 기반 추정이다. 한국어 사내 도메인은 OOD일 수 있다. 도입 전 자체 검증셋 100~500쌍으로 recall@10·P95를 측정하고, 외부 벤치와 1.5× 이상 어긋나면 Q5 silent failure 신호로 보고 모델·가중치·압축 방식을 다시 선택한다. 본 문서는 외부 벤치 측정만 인용하며, 사내 실측 사례는 측정 후 본 절을 갱신한다.

엔지니어가 LLM 운영을 할 때 토크나이저·임베딩 직관은 다음 판단에 바로 쓰인다.

  • 한국어 비용 견적: GPT-4 cl100k에서는 한국어가 영어 대비 2.53×, GPT-4o o200k에서는 1.52× 수준으로 개선될 수 있다. prompt 언어와 압축 전략이 비용에 영향을 준다.
  • context window 설계: 같은 8k window라도 tokenization 효율이 낮으면 실제로 넣을 수 있는 문서 길이가 줄어든다. 한국어가 영어 대비 2× 비효율이면 체감 입력 길이도 대략 절반으로 줄어든다.
  • 임베딩 모델 선택: 비용, 품질, privacy, 다국어, self-host 가능성을 분리해서 본다.
  • vector store 비용: Matryoshka와 quantization으로 차원·dtype을 줄이면 storage·메모리가 4×, 32×, 조합에 따라 100× 이상 줄 수 있다.
  • fine-tuning 안정성: base 모델 tokenizer와 chat template을 바꾸면 학습은 되는 것처럼 보여도 추론이 깨질 수 있다.
  • prompt injection 방어: <|im_start|> 같은 special token은 사용자 입력에서 제어 토큰으로 해석되지 않게 sanitize해야 한다.
  • RAG 밖의 활용: 임베딩은 검색뿐 아니라 clustering, 추천, 분류 baseline, 코드 검색에도 쓰인다. 공통 질문은 항상 “어떤 대상을 vector로 놓고 어떤 거리로 비교하나”다.
개념 A개념 B차이점
Word-levelBPEOOV 문제 vs subword 합병으로 OOV 완화
BPEbyte-level BPE문자/subword 합병 vs UTF-8 byte 256개를 base로 둠
BPESentencePiece알고리즘 계열 vs 언어 독립 tokenizer 학습 도구
WordPieceBPElikelihood 기반 subword 선택 vs 빈도 기반 쌍 합병
tiktokenHuggingFace tokenizerOpenAI 표준 구현 vs open-source tokenizer 생태계
Token embeddingSentence embeddingLLM 내부 token 표현 vs 검색용 문장·문서 단일 vector
Cosine similarityDot product방향 유사도 vs 내적. L2 정규화된 vector에서는 같아짐
Cosine similarityEuclidean방향 중심 vs 크기까지 포함한 위치 차이
PCAMatryoshka사후 투영 차원 축소 vs 학습 시 prefix 절단을 가정
PQBinary embeddingchunk별 codebook 압축 vs 각 차원을 1bit 수준으로 양자화
BM25Dense embedding정확한 어휘 매칭 vs 의미 거리
Sparse embeddingDense embedding대부분 0인 어휘 가중치 vector vs 밀집 실수 vector
ColBERTRerankermulti-vector late interaction vs 후보 재정렬 cross-encoder
ANN/HNSWReranker빠른 후보 검색 index vs 후보 안에서 정확 재정렬

11. 체크리스트 체크

  • BPE 알고리즘을 OOV 문제와 한국어 토큰 효율 관점에서 설명할 수 있다
  • "한국어가 영어보다 비싼" 이유를 byte-level BPE와 vocab 크기로 설명할 수 있다
  • special token과 tokenizer mismatch가 왜 silent failure인지 설명할 수 있다
  • Cosine similarity, dot product, Euclidean distance의 차이를 손계산 예시로 설명할 수 있다
  • 정규화된 임베딩에서 cosine과 dot product가 같아지는 이유를 말할 수 있다
  • MRL, PCA, PQ, int8, binary quantization의 차이를 설명할 수 있다
  • BM25, sparse embedding, dense embedding, ColBERT, RRF가 hybrid search에서 맡는 역할을 구분할 수 있다
  • reranker를 붙이기 전에 recall@100을 먼저 봐야 하는 이유를 설명할 수 있다
  • dual-index 없이 embedding 모델을 바꾸면 좌표계가 섞이는 이유를 설명할 수 있다
  • 토크나이저: BPE, SentencePiece, WordPiece, Unigram, byte-level BPE, tiktoken, HuggingFace tokenizers
  • 임베딩 모델: text-embedding-3, voyage, Cohere embed, BGE, gte, E5, Nomic, Snowflake Arctic
  • 임베딩 학습: contrastive learning, InfoNCE, SimCSE, hard negative mining, instruction tuning for embeddings
  • 차원·압축: Matryoshka, PCA, UMAP, Product Quantization, binary embedding, BinaryNet
  • vector store: FAISS, pgvector, Pinecone, Weaviate, Qdrant, Milvus, ChromaDB
  • 검색: BM25, hybrid search, reranking, ColBERT, RRF, SPLADE, HNSW

토크나이저 비교 체크

  • OpenAI tiktoken으로 같은 한국어/영어 문장을 토큰화 비교 — 자릿수 차이 직접 확인
  • tiktoken의 raw token 수와 실제 API usage의 billed token 수를 비교 — system message, chat template, tool wrapper가 추가 token을 만든다는 점 확인
  • HuggingFace `transformers`로 LLaMA-3 vs LLaMA-2 토크나이저로 한국어 토큰 수 비교 — Llama 3가 더 효율적인지 확인
  • OpenAI 토크나이저(`tiktoken.encoding_for_model("gpt-4")`)에서 `" hello"` vs `"hello"` token ID 비교 — 공백 차이 확인
  • 이모지와 한자가 섞인 문장을 cl100k_base와 o200k_base에서 비교 — byte fallback 비용 확인

임베딩 거리 체크

  • `text-embedding-3-small`로 5개 단어("개", "고양이", "비행기", "기차", "자동차")를 임베딩 후 cosine similarity 행렬 계산. "비행기-기차"가 "비행기-개"보다 가까운지 확인
  • 같은 모델로 원문과 번역문을 임베딩해 cosine similarity를 측정하고 무관 문장 pair 분포와 비교 — 0.7 같은 보편 threshold 대신 자체 cross-lingual retrieval 성능으로 판정
  • vector를 L2 정규화한 뒤 cosine과 dot product ranking이 같은지 확인
  • text-embedding-3-large(3072)로 1000개 문서 임베딩. 256/512/1024/3072 차원으로 잘라 RAG retrieval recall@10 비교 — 256에서도 큰 손실 없는지 확인
  • 같은 vector를 fp32, int8, binary로 저장해 storage와 recall@10 비교 — 평균뿐 아니라 한국어 고유명사 query slice를 따로 확인
  • dense-only top-10에서 고유명사 query가 실패하는 예시 20개를 모은다.
  • 같은 query를 BM25, dense, BM25+dense RRF로 비교한다.
  • top-100 recall이 충분한지 확인한 뒤 reranker를 붙여 top-10 개선 폭과 P95 latency 증가를 같이 본다.

Silent failure 진단 체크

  • HuggingFace로 fine-tune 후 추론에서 출력이 깨지는 경우 → tokenizer mismatch 점검 (`AutoTokenizer.from_pretrained` 경로)
  • LLM 출력에 `<|im_start|>` 같은 special token이 보이면 → chat template 손상 또는 sanitize 누락 의심
  • embedding 모델 버전 변경 시 공식 cross-model 호환 계약부터 확인 → 보장이 없으면 corpus 전체와 query를 별도 재임베딩하고, 보장이 있어도 dimension·quantization·retrieval shadow 평가 후 cutover
  • 한국어 token 수가 예상보다 많음 → tokenizer별로 다르다. LLaMA-2는 매우 비효율일 수 있고, LLaMA-3는 개선됐다. 실제 target 모델로 다시 측정한다.
  • 임베딩 검색 결과가 무관한 문서를 반환 → anisotropy, 도메인 mismatch, chunking 실패, 평가셋 부재를 먼저 의심한다. 그다음 reranker나 domain fine-tune을 검토한다.
  • Matryoshka 절단 후 품질 폭락 → MRL 미지원 모델일 가능성이 높다. OpenAI text-embedding-3, Cohere embed-v4, Voyage 4 계열처럼 공식 문서에 명시된 출력 차원과 native 지원 여부를 확인한다.
  • reranker를 붙였는데 개선이 없음 → top-100 후보 안에 정답이 없는 문제일 수 있다. retrieve 단계 recall부터 고친다.
  1. 토크나이저는 텍스트를 token ID 시퀀스로 바꾸고, 임베딩은 token·문장·이미지를 의미를 보존하는 vector로 바꾼다.
  2. BPE는 OOV를 subword 조합으로 줄이고, byte-level BPE는 UTF-8 byte를 base로 둬 모든 unicode를 표현한다.
  3. 한국어 token 비용은 tokenizer와 vocab에 따라 영어 대비 2.53×에서 1.52× 수준까지 달라질 수 있다.
  4. 임베딩 검색의 핵심 거리는 cosine/dot이고, dense만 약한 고유명사·법률·코드 영역은 BM25/sparse/RRF와 reranker로 보완한다.
  5. MRL·quantization·dual-index migration은 비용 최적화 기법이지만, recall@10·nDCG@10·exact match 같은 retrieval 지표 없이 적용하면 위험하다. 임베딩 공간은 기본적으로 모델 간 비호환이고 명시적 벤더 보장만 예외다. 예외에서도 cosine threshold가 아닌 dimension·quantization 계약과 retrieval 평가로 cutover를 승인한다.

최종 수정: 2026-07-14