Word-level
예시: 옛 NLP
단어 하나를 token으로 둔다. 어휘 폭증과 OOV 문제가 크다분류: Layer 11 - AI 기초 & 머신러닝 | 선수지식: L11-10 (ML 수학 토대), L11-50 (트랜스포머)
**토크나이저(tokenizer)**는 텍스트를 모델이 읽는 작은 조각인 **토큰(token)**의 ID 시퀀스로 바꾸는 모듈이다. **임베딩(embedding)**은 토큰·문장·이미지 같은 대상을 의미가 비슷한 것끼리 가까운 고차원 벡터로 바꾸는 표현 방식이다.
두 개념은 한 흐름에 있다. 토크나이저가 먼저 “무엇을 모델 입력 단위로 볼 것인가”를 정하고, 임베딩은 그 단위를 “어떤 좌표로 놓을 것인가”를 정한다. 그래서 LLM 비용, context window 실효 길이, fine-tuning 안정성, RAG 검색 품질이 모두 이 토픽 위에 올라간다.
처음에는 토크나이저와 임베딩을 별도 기능처럼 보기 쉽다. 실제 운영에서는 세 질문으로 계속 다시 만난다.
이 문서의 목표는 도구 이름을 외우는 것이 아니다. “텍스트가 토큰이 되고, 토큰·문장이 벡터가 되고, 벡터 거리가 검색 품질과 비용 판단으로 이어진다”는 흐름을 손으로 설명할 수 있게 만드는 것이다.
frontmatter lineage_oneliner: "토큰화 OOV 문제를 BPE로 해결하고 임베딩으로 의미거리 기반 검색 지원"의 본문 근거.
[UNK]가 된다. 이 OOV 문제 때문에 신조어·고유명사·오타를 만날 때마다 의미가 사라질 수 있다.bytes_to_unicode() 주석). GPT-2 50,257 vocab, GPT-4o ~200k vocab(§3.4)이 모두 이 흐름 위에 있다.핵심은 “단어를 외워서 처리한다”에서 “단어를 조합 가능한 부품으로 분해한다”로 바뀐 점이다. 이 전환이 없으면 한국어, 이모지, 코드 식별자, 새 제품명처럼 계속 생기는 문자열을 안정적으로 다루기 어렵다.
이 토픽이 비면 LLM API 비용 견적, context window 실효 활용, RAG 검색 품질, 다국어 처리 결정의 토대가 같이 빈다. 한국어 토큰 비용 2~3× 차이도 byte-level BPE 계보를 모르면 “왜?”에 답하기 어렵다.
토크나이저 문서를 읽을 때 가장 먼저 헷갈리는 지점은 “단어, 글자, byte, subword가 모두 token이 될 수 있다”는 점이다.
[UNK]로 떨어질 수 있고, subword·byte-level 계열에서는 여러 조각으로 분해해 표현한다.<|im_start|>, <|endoftext|>처럼 일반 사용자 텍스트가 아니라 모델 제어·chat template 경계를 나타내는 token이다. raw user input에 섞이면 prompt injection이나 출력 혼란으로 이어질 수 있다.§2.5의 word-level → BPE → byte-level BPE 계보를 구현 단위로 펼치면 다음과 같다. BPE, SentencePiece, WordPiece는 모두 “단어를 더 작은 조각으로 나누되 너무 잘게 쪼개지는 비용은 줄이자”는 subword 계열이다.
예시: 옛 NLP
단어 하나를 token으로 둔다. 어휘 폭증과 OOV 문제가 크다예시: 글자 단위 모델
OOV는 줄지만 sequence가 길어진다. 긴 입력 비용이 커진다예시: GPT, LLaMA, Mistral
빈도 높은 쌍을 반복 합병한다. vocab 크기와 sequence 길이의 균형점예시: T5, ALBERT, mT5
언어별 공백 규칙에 덜 의존하는 tokenizer 학습 도구. BPE와 Unigram을 지원예시: BERT, DistilBERT
likelihood 기반 subword 선택. BERT 계열에서 `##` 접두로 이어지는 조각을 표시예시: OpenAI GPT-3.5/4/o
OpenAI의 빠른 BPE 구현. byte-level BPE와 regex pre-tokenization을 사용현대 LLM 표준은 byte-level BPE에 가깝다. 모든 unicode를 처리할 수 있고 OOV가 없다는 장점 때문이다. 대신 어떤 언어가 학습 코퍼스와 vocab에서 덜 잘 합병되었는지에 따라 token 수 차이가 커진다.
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) | Llama 2 대비 평균 ~15% 토큰 절감 | |
| 일본어 | GPT-4 | ~2× | |
| 중국어 | GPT-4 | ~1.5× | 한 글자 정보 밀도 ↑ |
한국어가 비싸지는 흐름은 이렇게 읽으면 된다.
직접 측정해 보자. 이 예제는 token 효율을 감으로 말하지 않고 숫자로 보는 최소 실습이다 (openai/tiktoken).
# pip install tiktokenimport 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가 생길 수 있다.
임베딩은 대상을 숫자 벡터로 바꾸는 함수다.
"고양이" -> [0.21, -0.18, 0.55, ...]"강아지" -> [0.19, -0.20, 0.58, ...]"비행기" -> [-0.42, 0.71, 0.03, ...]중요한 점은 각 차원이 사람이 붙인 “귀여움”, “탈것” 같은 명시적 의미를 갖지 않는다는 것이다. 보통은 사람이 해석하기 어려운 수백~수천 개 숫자 좌표이고, 학습 목표는 비슷한 의미가 가까운 위치에 오도록 만드는 것이다.
검색에서 주로 쓰는 것은 sentence embedding이다. token embedding은 LLM 내부 계산의 재료이고, sentence embedding은 외부 검색 시스템에서 “이 query와 이 문서가 가까운가”를 빠르게 비교하는 도구다.
좋은 임베딩 공간은 세 성질을 기대한다.
king - man + woman ≈ queen 같은 word2vec 예시는 역사적으로 유명하다. 현대 sentence embedding에서는 이 직관을 과하게 일반화하면 안 되지만, “좌표 차이가 의미 차이를 일부 담는다”는 출발점으로는 유용하다.경계도 있다. **anisotropy(비등방성)**는 임베딩이 넓게 퍼지지 않고 좁은 cone에 몰리는 현상이다. 이때는 모든 cosine similarity가 비슷하게 높아져 거리 차이가 의미 차이를 잘 구분하지 못한다. contrastive learning은 positive를 가깝게, negative를 멀게 밀어 이런 쏠림을 완화하려는 학습 압력을 만든다.
L11-10에서 본 벡터 수학을 검색 언어로 바꾸면 다음 표가 된다.
| 거리 | 식 | 사용 |
|---|---|---|
| Cosine similarity | (a · b) / (||a|| ||b||) | 방향만 — RAG·검색 표준 |
| Euclidean | ||a - b||_2 | 위치와 크기 — 임베딩 검색에서는 덜 흔함 |
| Dot product | a · b | 정규화된 임베딩에서는 cosine과 같음 |
| Inner product | dot product의 일반 표현 | FAISS·pgvector 인덱스 옵션 |
작은 숫자로 확인해 보자.
a = [3, 4]b = [6, 8]
a · b = 3*6 + 4*8 = 50||a|| = 5||b|| = 10cos(a, b) = 50 / (5*10) = 1b는 a를 2배 늘린 벡터라 방향이 완전히 같다. cosine은 방향만 보므로 1이다. 반대로 길이는 같아도 방향이 다르면 비슷하다고 볼 수 없다.
a = [3, 4]c = [-4, 3]
a · c = 3*(-4) + 4*3 = 0||a|| = 5||c|| = 5cos(a, c) = 0 / (5*5) = 0이 반례가 검색에서 중요하다. 문서 vector의 크기가 크다고 query와 관련 있는 것이 아니다. 보통 LLM 임베딩 모델은 L2 정규화된 vector를 내보내거나 정규화를 권장한다. 정규화된 vector에서는 ||a|| = ||b|| = 1이므로 cosine과 dot product가 같아지고, dot product 검색이 계산상 더 빠르다.
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·언어 구성을 섞어 절대 순위로 읽지 않는다.
| 모델 | 차원 | Context | MTEB | 특징 |
|---|---|---|---|---|
| OpenAI text-embedding-3-small | 1536 | 8k | 62.3 | 가성비. Matryoshka. $0.02/1M |
| OpenAI text-embedding-3-large | 3072 | 8k | 64.6 | OpenAI 생태계 default. Matryoshka. $0.13/1M |
| Cohere embed-v4 (2025) | up to 1536 | 128k | - | 다국어, 멀티모달, MRL |
| Voyage AI voyage-4-large | 256~2048 | 32k | 자체 검증 | Voyage가 현재 general-purpose·multilingual 최고 품질 후보로 안내. MRL + int8/binary |
| BGE-M3 (2024-01) | 1024 | 8k | ~63 | 공식 dense 출력은 1024d. dense+sparse+ColBERT 모드 |
| NV-Embed-v2 (NVIDIA) | 4096 | 32k | 72.3 | 공식 카드 기준 Mistral-7B-v0.1 기반, decoder-as-encoder |
| Qwen3-Embedding-8B | 32~4096 (flex) | 32k | 70.6 | flexible dim, 다국어 강함 |
| Stella / GritLM / mxbai-embed | 다양 | 다양 | 60~67 | open-weight 경량 |
| Gemini Embedding 2 (Google) | 768~3072 | 8k+ | 65+ | Google 진영 |
2025년 전후 공개 벤치 기준으로는 open-weight + decoder-as-encoder 계열(E5-mistral, NV-Embed, GritLM)이 강하게 올라왔다. 이 순위는 시간이 지나면 바뀔 수 있으므로, 표는 “현재 1등 외우기”가 아니라 모델을 고를 때 볼 축을 익히는 용도로 읽는다. OpenAI 3-large는 “절대 최고 품질”이라기보다 OpenAI API 생태계 안에서 기본 선택지로 보기 쉽다.
선택 기준은 네 축으로 압축된다.
text-embedding-3-small(비용) 또는 3-large(품질)를 먼저 비교한다.voyage-4-large, open-weight MRL 후보인 Qwen3-Embedding, 1024d multi-mode 후보인 BGE-M3를 같은 자체 평가셋에서 검토한다.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여기서 네 가지 압축 계열을 구분해야 한다.
Matryoshka worked example:
일반 임베딩: 1536차원 vector를 그대로 사용Matryoshka: 256, 512, 768, 1024, 1536 절단점에서도 검색 품질을 유지하도록 학습OpenAI text-embedding-3-large는 dimensions 파라미터로 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 1024d와 MRL 지원 모델의 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를 같이 보고 결정한다.
Dense embedding은 대부분의 차원이 0이 아닌 일반적인 실수 vector다. 자연어 의미 검색에는 강하지만, 다음 영역에서는 약해질 수 있다.
이 영역에서는 “의미가 비슷한 문서”보다 “정확한 문자열이 들어 있는 문서”가 먼저 필요할 수 있다. 그래서 lexical search와 dense search를 섞는다.
첫 등장 용어를 먼저 정리한다.
1 / (k + rank) 형태로 합쳐 순위를 결합하는 방법이다. 절대 점수 스케일이 달라도 rank만으로 섞을 수 있다.RRF를 작은 예시로 보자. k=60이고 query가 “SAML SSO ADFS 0x800…”처럼 고유명사와 에러 코드가 중요한 경우다.
| 문서 | dense rank | BM25 rank | RRF 점수 개념 |
|---|---|---|---|
| A: 일반 로그인 가이드 | 1 | 20 | 1/61 + 1/80 |
| B: ADFS 에러 코드 문서 | 6 | 1 | 1/66 + 1/61 |
| C: SSO 용어집 | 12 | 2 | 1/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으로 재정렬 (정확)운영 표준 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는 후보 안에서만 고칠 수 있다”는 실패 원리를 잡는 것이 더 중요하다.
아래 표는 “무엇이 좋은가”보다 “어떤 조건에서 깨지는가”를 보는 표다. 표의 숫자는 특정 모델·corpus·hardware에서 나올 수 있는 가상 관찰 예이며, 바로 적용할 정책이나 보편 임계값이 아니다. 자체 검증셋의 sweep 범위를 정하는 출발점으로만 쓴다.
| 기법 | 효과 발휘 범위 | 깨지는 조건 |
|---|---|---|
| Dense embedding only | 일반 자연어 QA | 고유명사·법률·코드·신조어 多 → BM25 가중치 ↑ |
| BM25 only | 키워드 정확 매칭 | paraphrase·의미 검색 → dense 보강 필요 |
| Hybrid (RRF k=60) | 일반 도메인 | 고유명사 압도 도메인 → α 0.4:0.6 (BM25 우위) |
| Reranker | top-100 → top-10 재정렬 | top-10 이미 정확하면 50~100ms 추가 손해 |
| MRL 지원 모델 256d | 가상 관찰에서 quality 1~3% 손실 | 더 짧은 차원의 손실 곡선은 모델·corpus별 재측정 |
| int8 quantization | 4× storage ↓, 품질 ~100% | 작은 모델·짧은 차원에선 효과 미미 |
| Binary embedding | 32× storage·40× 속도 | 단독·fp32 rescore 후보를 같은 recall·latency 예산에서 비교 |
| KoSimCSE·BGE-M3-ko | 한국어 전용 도메인 | 다국어 cross-lingual 검색엔 다국어 모델 우위 |
여기서 α 0.4:0.6은 dense 점수와 BM25 점수를 섞는 가중치 예시다. 고유명사·법령 번호·코드 식별자처럼 정확한 문자열 매칭이 중요하면 BM25 쪽 비중을 높이고, paraphrase가 많으면 dense 쪽 비중을 높인다.
읽는 순서는 이렇다.
| 증상 | 정량 시그널 | 원인 | 대응 원리 |
|---|---|---|---|
| 한국어 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 체계가 학습 때와 달라진 것이다.
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 평가를 대신하지 못한다.
학습 관점에서 안전한 절차는 다음 순서다.
90%는 진행 이정표일 수 있지만 cutover 조건이 아니다. 예상 문서·chunk가 100% 채워졌거나 제외 사유가 명시돼야 한다. Voyage 4 계열처럼 보장이 있으면 full re-embedding을 생략할 후보가 될 수 있지만, 동일한 지원 차원·표현과 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질문으로 분해한다.
아래 시나리오는 §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 tokensOpenAI 3-large: 10B / 1M * $0.13 = $1,300Voyage, 무료 소진: 10B / 1M * $0.12 = $1,200Voyage, 200M 잔여: (10B - 200M) / 1M * $0.12 = $1,176평균 문서 길이에 따른 민감도는 다음과 같다.
| 평균 토큰/문서 | 1M 문서의 총 토큰 | OpenAI $0.13/1M | Voyage 한계비용 (무료 소진) | Voyage 초기 비용 (200M 전부 잔여) |
|---|---|---|---|---|
| 1k | 1B | $130 | $120 | (1B-200M)×$0.12/1M = $96 |
| 10k | 10B | $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 생태계, Matryoshka | API: $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/binary | 10k tokens/doc 가정의 1M docs: 무료 소진 후 $1,200, account의 200M이 전부 남으면 $1,176. 저장: 1M vectors fp32 약 4GB | 무료 잔여량·API 가격 변동과 외부 의존 |
| D. C의 256d + binary + 2-stage rerank | native 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를 더 줄일 여지가 있음기대치와 책임 범위:
voyage-4-large가 공식 지원하는 1024d→256d로 4×, fp32→binary로 32×를 곱한 raw payload 계산이다. 1536d fp32 원본과 비교하면 192× 절감 감각이지만 metadata·index·replica는 별도다.이 수치는 외부 벤치 기반 추정이다. 한국어 사내 도메인은 OOD일 수 있다. 도입 전 자체 검증셋 100~500쌍으로 recall@10·P95를 측정하고, 외부 벤치와 1.5× 이상 어긋나면 Q5 silent failure 신호로 보고 모델·가중치·압축 방식을 다시 선택한다. 본 문서는 외부 벤치 측정만 인용하며, 사내 실측 사례는 측정 후 본 절을 갱신한다.
엔지니어가 LLM 운영을 할 때 토크나이저·임베딩 직관은 다음 판단에 바로 쓰인다.
<|im_start|> 같은 special token은 사용자 입력에서 제어 토큰으로 해석되지 않게 sanitize해야 한다.| 개념 A | 개념 B | 차이점 |
|---|---|---|
| Word-level | BPE | OOV 문제 vs subword 합병으로 OOV 완화 |
| BPE | byte-level BPE | 문자/subword 합병 vs UTF-8 byte 256개를 base로 둠 |
| BPE | SentencePiece | 알고리즘 계열 vs 언어 독립 tokenizer 학습 도구 |
| WordPiece | BPE | likelihood 기반 subword 선택 vs 빈도 기반 쌍 합병 |
| tiktoken | HuggingFace tokenizer | OpenAI 표준 구현 vs open-source tokenizer 생태계 |
| Token embedding | Sentence embedding | LLM 내부 token 표현 vs 검색용 문장·문서 단일 vector |
| Cosine similarity | Dot product | 방향 유사도 vs 내적. L2 정규화된 vector에서는 같아짐 |
| Cosine similarity | Euclidean | 방향 중심 vs 크기까지 포함한 위치 차이 |
| PCA | Matryoshka | 사후 투영 차원 축소 vs 학습 시 prefix 절단을 가정 |
| PQ | Binary embedding | chunk별 codebook 압축 vs 각 차원을 1bit 수준으로 양자화 |
| BM25 | Dense embedding | 정확한 어휘 매칭 vs 의미 거리 |
| Sparse embedding | Dense embedding | 대부분 0인 어휘 가중치 vector vs 밀집 실수 vector |
| ColBERT | Reranker | multi-vector late interaction vs 후보 재정렬 cross-encoder |
| ANN/HNSW | Reranker | 빠른 후보 검색 index vs 후보 안에서 정확 재정렬 |
text-embedding-3-large(3072)로 1000개 문서 임베딩. 256/512/1024/3072 차원으로 잘라 RAG retrieval recall@10 비교 — 256에서도 큰 손실 없는지 확인최종 수정: 2026-07-14