한국어 Wiki 임베딩 설계: 모델, 청크, 차원, 인덱싱과 검색 속도
한국어 Wiki를 벡터 검색으로 만들 때 모델, 청크 크기, 임베딩 차원, 인덱싱과 실시간 검색 속도를 어떻게 결정하는지 설명합니다.
한국어 Wiki를 임베딩할 때 가장 먼저 정해야 할 값은 모델 이름이 아닙니다. 먼저 어떤 질문으로 어떤 크기의 지식 조각을 찾을 것인지 정해야 합니다. 그다음 청크를 만들고, 그 청크를 잘 구분하는 모델과 차원을 고릅니다.
실무에서 출발점으로 삼기 좋은 설정은 다음과 같습니다.
| 항목 | 권장 시작값과 조정 기준 |
|---|---|
| 분할 | Markdown H2, H3 제목, 제목이 없을 때 문단 경계 추가 |
| 크기 | 300~600토큰, 짧은 문답은 128~300, 넓은 근거는 600~1,000 |
| 상한 | 800토큰, 표, 코드, 정의와 설명을 보존할 때만 일시적으로 초과 |
| overlap | 기본 0%, 같은 절을 길이 때문에 나눌 때만 5~10% |
| 차원 | 768 또는 1,024, 10만 청크 이상이면 256, 512도 평가 |
| 후보 | dense top 20~50, reranker가 없으면 적게, 어려운 검색이면 많이 |
| 최종 문맥 | rerank 후 5개 안팎, 필요하면 parent, 인접 청크 확장 |
이 숫자는 정답이 아니라 첫 번째 실험점입니다. Wiki마다 문서 길이, 질문의 구체성, 한영 혼합 비율이 다르므로 실제 질문 세트로 검증해야 합니다.

임베딩 검색은 두 번의 다른 일을 한다
임베딩 검색을 이해하려면 인덱싱과 검색을 분리해야 합니다.
인덱싱은 Wiki 글이 추가되거나 바뀔 때 수행합니다.
- Markdown에서 제목, 본문, 표, 코드와 metadata를 읽습니다.
- 문서를 검색 가능한 청크로 나눕니다.
- 각 청크를 임베딩 모델에 넣어 숫자 벡터를 얻습니다.
- 벡터, 청크 ID, 원문, 제목 경로, URL을 벡터 DB에 저장합니다.
- 데이터가 크면 HNSW 같은 ANN 인덱스를 구성합니다.
검색은 사용자가 질문할 때마다 수행합니다.
- 질문을 문서와 같은 임베딩 공간의 벡터로 만듭니다.
- 질문 벡터와 가까운 문서 벡터를 찾습니다.
- 벡터 DB가 청크 ID와 유사도 점수를 반환합니다.
- ID에 연결된 원문을 payload나 별도 문서 저장소에서 읽습니다.
- 필요하면 reranker로 순서를 다시 정하고, parent 절이나 인접 청크를 붙입니다.
중요한 점은 벡터에 본문 문장이 압축 파일처럼 들어 있는 것이 아니라는 사실입니다. 768개의 실수로 된 벡터에서 원문을 복원하지 않습니다. 벡터는 가까운 지식 조각을 가리키는 좌표이고, 실제 문장은 그 좌표에 연결해 둔 원문 저장소에서 가져옵니다.
한국어 Wiki에 맞는 임베딩 모델 고르기
한국어 Wiki에는 다국어 retrieval 학습을 받은 모델을 우선 검토합니다. 단순히 한국어 문장을 입력할 수 있다는 것과 한국어 질문으로 관련 문서를 잘 찾는다는 것은 다릅니다.
2026년 8월 기준으로 비교하기 좋은 후보는 다음과 같습니다.
| 모델 | 공식 사양과 적합한 상황 |
|---|---|
OpenAI text-embedding-3-small | API, 8,192토큰, 차원 축소 지원. 빠른 구축과 운영 최소화에 적합 |
OpenAI text-embedding-3-large | API, 8,192토큰, 차원 축소 지원. 비영어 검색 품질을 우선할 때 비교 |
gemini-embedding-2 | API, 8,192토큰, 128~3,072차원. Google API와 멀티모달 확장에 적합 |
Qwen3-Embedding-0.6B | 로컬, 32K, 32~1,024차원, MRL. 개인정보와 비용을 직접 통제할 때 적합 |
BGE-M3 | 로컬, 8,192토큰, 1,024차원. dense, sparse, multi-vector를 함께 시험할 때 적합 |
OpenAI API 문서는 현재 임베딩 요청에서 모델별 입력이 8,192토큰을 넘을 수 없고, 한 요청에 담긴 전체 입력은 300,000토큰을 넘을 수 없다고 명시합니다. text-embedding-3 이후 모델은 dimensions 매개변수로 출력 차원을 줄일 수 있습니다. OpenAI Embeddings API
Google 문서에서 gemini-embedding-2는 8,192토큰과 128~3,072차원을 지원하며, 768, 1,536, 3,072차원을 권장합니다. 3,072보다 작은 차원도 자동 정규화합니다. Gemini Embeddings
Qwen 공식 모델 카드는 0.6B, 4B, 8B 모델을 제공하고, 각각 최대 1,024, 2,560, 4,096차원을 지원한다고 설명합니다. 세 모델 모두 MRL과 task instruction을 지원합니다. 0.6B 모델도 32K 입력을 받을 수 있지만, 32K 전체를 한 Wiki 청크로 쓰라는 뜻은 아닙니다. Qwen3-Embedding-0.6B
BGE-M3는 1,024차원, 8,192토큰이며 dense, sparse, multi-vector 검색을 함께 실험할 수 있습니다. 모델 카드 예제는 FP16이 계산을 빠르게 하지만 성능이 약간 낮아질 수 있다고 안내합니다. BGE-M3
API와 로컬 중 무엇이 맞을까
| 상황 | 추천과 이유 |
|---|---|
| 작은 공개 Wiki를 빨리 구축 | API 소형 모델, 서버와 GPU 운영이 필요 없음 |
| 비공개 문서를 외부로 보낼 수 없음 | 로컬 모델, 원문과 질문을 내부에 유지 |
| 인덱싱과 검색 요청이 적음 | API, 유휴 GPU 없이 사용량만 지불 |
| 문서 유입과 검색량이 큼 | 로컬 또는 전용 endpoint, 충분한 batch에서 비용 절감 가능 |
| 제품 코드, 약어가 중요 | dense와 BM25 또는 sparse 병합, 정확한 문자열 검색 보강 |
| 품질 우선, 지연시간 허용 | 큰 embedding 모델과 reranker, 후보 생성과 정밀 순위화 분리 |
현재 규모가 작다면 API와 로컬의 품질 차이보다 청크가 답을 온전히 담는지, 질문과 문서 입력 형식을 모델 지침대로 썼는지가 더 큰 차이를 만들 수 있습니다.
모델 instruction을 빼먹지 않는다
일부 모델은 질문과 문서를 서로 다르게 표시하도록 학습됩니다. Qwen3는 검색 목적을 설명하는 instruction을 지원하고, Gemini Embedding 2는 text-only 검색에서 task instruction을 프롬프트에 넣도록 권장합니다. 반면 BGE-M3 모델 카드는 일반 dense retrieval에서 별도 query instruction이 필요하지 않다고 설명합니다.
모델을 바꾸면서 prefix 규칙을 그대로 복사하면 품질이 떨어질 수 있습니다. 모델마다 다음 세 가지를 함께 버전으로 관리해야 합니다.
- 모델 ID와 revision
- query, document 입력 템플릿
- 출력 차원과 정규화 방식
Wiki 페이지 전체를 한 청크로 넣을까
짧은 페이지라면 가능합니다. 하지만 일반적인 Wiki는 페이지 하나에 정의, 배경, 동작 방식, 비교, 예제가 함께 들어갑니다. 이 전체를 한 벡터로 평균 내면 개별 사실의 신호가 약해집니다.
가령 HNSW 페이지에 정의, 그래프 구성, M, ef_construct, ef_search, 메모리 계산, Qdrant 예제가 모두 있다고 해보겠습니다. “HNSW가 무엇인가?”라는 넓은 질문에는 페이지 전체 벡터도 잘 맞을 수 있습니다. 하지만 “ef_construct를 높이면 무엇이 느려지는가?”라는 질문은 해당 절의 작은 벡터가 더 분명하게 반응합니다.

페이지 전체 1청크가 맞는 경우
- 본문이 약 300~500토큰 이하입니다.
- 페이지가 하나의 정의나 하나의 문답만 다룹니다.
- 제목을 제외해도 내용이 독립적으로 이해됩니다.
- 검색 결과로 페이지 전체를 읽게 하는 것이 목적입니다.
제목 기반 분할이 맞는 경우
- H2, H3마다 독립된 질문에 답합니다.
- 한 페이지에 여러 설정값, 비교, 예제가 있습니다.
- 특정 문장이나 절을 정확히 찾는 검색이 많습니다.
- 페이지 길이가 모델 한도보다 짧더라도 의미가 여러 갈래입니다.
긴 Wiki의 권장 구조는 요약과 섹션의 이중 인덱스다
페이지를 통째로 넣는 방식과 잘게 나누는 방식 중 하나만 선택할 필요는 없습니다.
- page summary vector는 “HNSW 전체 개념”, “한국어 임베딩 설계” 같은 넓은 질문을 받습니다.
- section chunk vector는 “
ef_search기본값”, “768차원 저장량” 같은 구체적 질문을 받습니다.
두 결과를 합칠 때는 같은 페이지가 결과를 독점하지 않도록 페이지별 후보 수를 제한하거나 RRF, reranker를 사용합니다. 요약은 원문 전체를 그대로 임베딩한 벡터보다 페이지의 목적과 핵심 절을 짧게 정리한 별도 텍스트가 다루기 쉽습니다.
한국어 Wiki 청크는 어떻게 자를까
기본 순서는 다음과 같습니다.
- frontmatter와 본문을 분리합니다.
- H2, H3 제목 경계로 section을 만듭니다.
- 각 section에 페이지 제목과 상위 제목 경로를 붙입니다.
- 800토큰을 넘는 section만 문단 또는 문장 경계로 다시 나눕니다.
- 100토큰도 되지 않는 짧은 section은 같은 주제의 인접 section과 합칩니다.
- 표, 코드 블록, 정의와 바로 뒤의 설명은 중간에서 자르지 않습니다.
벡터화할 실제 텍스트는 다음처럼 구성할 수 있습니다.
문서: 한국어 Wiki 임베딩 설계
경로: Wiki 청크는 어떻게 자를까 > 제목 기반 분할
H2와 H3 제목을 먼저 의미 경계로 사용한다. 800토큰을 넘는 절만 문단 경계로 다시 나눈다.
payload에는 다음 값을 별도로 둡니다.
{
"document_id": "korean-wiki-embedding-guide",
"chunk_id": "korean-wiki-embedding-guide:h2-04:0002",
"heading_path": ["Wiki 청크는 어떻게 자를까", "제목 기반 분할"],
"chunk_order": 7,
"url": "/posts/korean-wiki-embedding-guide/#제목-기반-분할",
"embedding_model": "model-and-revision",
"embedding_dimension": 768,
"content_hash": "..."
}
overlap은 보험이지만 공짜가 아니다
고정 길이로 아무 곳에서나 자르면 경계에 걸린 문장이 사라질 수 있어 overlap이 도움이 됩니다. 하지만 제목과 문단 경계를 지키면 중복이 꼭 필요하지 않습니다.
20% overlap을 쓰면 대략 20%에 가까운 중복 토큰을 다시 임베딩하고 저장합니다. 검색 결과에도 같은 문장이 든 청크가 여러 개 나타나기 쉽습니다. 따라서 Wiki의 기본 overlap은 0%로 두고, 같은 긴 section을 둘로 나눌 때만 5~10%를 시작값으로 권합니다.
2026년 한 chunking 분석은 Natural Questions 기반 설정에서 overlap이 측정 가능한 이득 없이 인덱싱 비용을 늘렸다고 보고했습니다. 다만 한 데이터셋과 retrieval 구성에서 나온 결과이므로 모든 Wiki에 그대로 적용할 법칙은 아닙니다. A Systematic Analysis of Chunking Strategies for Reliable Question Answering
질문 종류가 청크 크기를 결정한다
| 주된 질문 | 시작 크기와 이유 |
|---|---|
| 용어 정의, 숫자, 단일 사실 | 128~300토큰, 다른 주제가 섞이기 전에 근거 포착 |
| 일반적인 기술 Wiki 검색 | 300~600토큰, 정의, 설명, 짧은 예제를 함께 보존 |
| 원인 분석, 절차, 비교 | 600~1,000토큰, 여러 문장을 함께 읽어야 답이 성립 |
| 페이지 전체 요약 | section과 별도 summary, 세부 검색과 넓은 검색을 분리 |
장문 retrieval 연구에서도 짧은 사실형 답에는 64~128토큰, 넓은 문맥에는 512~1,024토큰이 더 유리한 경향이 보고됐습니다. 핵심은 하나의 숫자가 모든 데이터셋과 모델에서 이기지 않는다는 점입니다. Rethinking Chunk Size For Long-Document Retrieval
검색용 child와 답변용 parent를 분리한다
작은 청크는 찾기 쉽지만 답변에 필요한 문맥이 부족할 수 있습니다. 이때 검색 청크 자체를 크게 만들기보다 다음 흐름이 안정적입니다.
- 300~600토큰 child 청크로 검색합니다.
- reranker가 최종 child를 고릅니다.
- 같은 H2 parent section 또는 앞뒤 한 청크를 가져옵니다.
- 중복을 제거한 뒤 LLM context 예산에 맞춰 전달합니다.
이 구조는 “찾기 좋은 크기”와 “읽기 좋은 크기”를 따로 최적화합니다. 긴 문서를 먼저 token-level로 인코딩한 뒤 pooling 직전에 청크를 만드는 late chunking도 주변 문맥을 보존하려는 접근입니다. 다만 모든 API가 token embedding을 노출하는 것은 아니므로 일반적인 API RAG에서는 parent-child 방식이 구현하기 쉽습니다. Late Chunking
모델이 크면 왜 느려질까
임베딩 모델은 입력 토큰을 여러 Transformer layer로 통과시킨 뒤 pooling과 projection을 수행합니다. 모델이 커진다는 것은 보통 layer 수, hidden size, feed-forward width, attention head 중 하나 이상이 커진다는 뜻입니다.
표준 self-attention 한 layer의 계산량은 시퀀스 길이를 , hidden dimension을 라고 할 때 대략 다음 항을 가집니다.
원 Transformer 논문은 self-attention layer의 복잡도를 로 정리합니다. Attention Is All You Need
여기에 Q, K, V projection과 feed-forward network의 대략적인 계산도 있습니다. 그래서 “토큰 수를 두 배로 하면 전체 모델이 정확히 네 배 느려진다”고 단정하면 안 됩니다. attention 부분은 네 배 규모로 커질 수 있지만, 실제 wall-clock은 feed-forward, FlashAttention, 메모리 대역폭, kernel 최적화, batch에 함께 좌우됩니다.

파라미터가 많을수록 대체로 느리지만 예외가 생기는 이유
- 작은 모델도 CPU에서 실행하고 큰 모델은 고성능 GPU에서 실행하면 큰 모델이 더 빠를 수 있습니다.
- BERT 계열 encoder와 decoder 기반 embedding 모델은 pooling 방식과 실행 kernel이 다릅니다.
- FP32, BF16, FP16, INT8, INT4는 메모리 사용량과 처리량이 다릅니다.
- FlashAttention, fused kernel, ONNX, TensorRT, TEI, vLLM 같은 runtime 최적화 차이가 큽니다.
- batch 1의 지연시간과 batch 64의 초당 처리량은 다른 지표입니다.
- API는 모델 계산 외에도 네트워크, 대기열, rate limit 영향을 받습니다.
따라서 모델 속도 표에는 최소한 hardware, dtype, batch size, 입력 길이 분포, warm-up 여부를 함께 적어야 합니다. “0.6B는 8B보다 몇 배 빠르다”는 숫자를 환경 없이 옮겨 적는 것은 의미가 없습니다.
같은 batch에서는 길이가 비슷한 청크끼리 묶는다
batch 안의 입력 길이가 100, 120, 140, 800토큰이면 짧은 세 입력도 800토큰에 맞춰 padding될 수 있습니다. 계산하지 않아도 될 padding이 처리량을 잡아먹습니다.
인덱싱할 때는 다음처럼 길이 bucket을 두는 편이 낫습니다.
- 0~256토큰
- 257~512토큰
- 513~800토큰
- 그 이상 예외 청크
각 bucket 안에서 GPU 메모리가 허용하는 최대 batch를 찾습니다. 실시간 질문은 대개 짧으므로 문서 인덱싱과 별도의 batch, latency 설정을 사용합니다.
인덱싱 속도는 어디서 결정될까
전체 인덱싱 시간은 다음처럼 나눌 수 있습니다.
- 는 Markdown, 표, 코드와 metadata를 읽는 시간입니다.
- 는 제목, 문단, token 기준으로 분할하는 시간입니다.
- 는 모델 inference 또는 API 호출 시간입니다.
- 는 벡터와 payload를 저장하는 시간입니다.
- 는 HNSW 같은 ANN 인덱스를 구성하는 시간입니다.
작은 Wiki에서는 대개 모델 로딩 또는 첫 API 요청이 눈에 띕니다. 청크가 수백 개라면 HNSW를 정교하게 튜닝해도 체감 차이가 작습니다. 반대로 청크가 수십만 개가 되면 embedding batch, 벡터 전송, HNSW build와 메모리가 중요해집니다.
전체를 매번 다시 임베딩하지 않는다
각 문서와 청크에 다음 버전 정보를 둡니다.
- 정규화한 본문
content_hash - chunker 버전과 설정
- embedding 모델과 revision
- query, document 템플릿 버전
- 출력 차원과 정규화 방식
글 한 절만 바뀌었다면 hash가 달라진 청크만 다시 임베딩합니다. 삭제된 청크 ID는 벡터 DB에서도 제거합니다. 입력이 같으면 같은 ID가 나오도록 결정적 ID를 사용하면 upsert와 중복 제거가 단순해집니다.
모델이나 차원을 바꾸면 기존 벡터와 새 벡터를 같은 공간에서 비교할 수 없습니다. 새 collection에 전체를 다시 만들고 검증한 다음 alias를 바꾸는 blue-green 전환이 안전합니다.
API 인덱싱은 요청 수보다 토큰과 제한을 함께 본다
API batch에 많은 청크를 넣으면 HTTP 왕복은 줄지만 서비스별로 입력 개수, 요청 전체 토큰, rate limit이 있습니다. OpenAI의 현재 Embeddings API는 한 입력당 최대 8,192토큰, 한 요청 전체 최대 300,000토큰을 명시합니다. 이 한도 안에서도 timeout과 재시도를 고려해 지나치게 큰 요청 하나보다 재시도 가능한 중간 크기 batch가 운영하기 쉽습니다.
실제 검색은 어느 단계가 느릴까
검색 한 번의 지연시간은 다음처럼 나눕니다.

1. 질문 임베딩
질문 하나를 문서와 같은 모델, 같은 차원으로 변환합니다. 로컬에서는 batch 1 inference 지연, API에서는 네트워크 왕복과 공급자 대기시간이 포함됩니다. 작은 Wiki에서는 이 단계가 ANN 검색보다 더 오래 걸릴 수 있습니다.
질문 벡터는 동일한 질문이 반복될 때 cache할 수 있지만, 사용자별 권한 filter와 검색 옵션은 별도 cache key에 포함해야 합니다.
2. ANN 또는 exact 검색
벡터가 적으면 모든 벡터와 직접 비교하는 exact search도 충분합니다. 이 방식은 단순하고 결과가 안정적이며 ANN recall 손실이 없습니다. 데이터가 커지면 HNSW가 모든 벡터를 읽지 않고 그래프를 따라 가까운 후보를 찾습니다.
Qdrant는 dense vector index로 HNSW를 사용하며, 작은 검색 범위에서는 full scan을 선택할 수 있습니다. 공식 문서의 기본 예시는 m=16, ef_construct=100입니다. m을 높이면 정확도와 메모리가, ef_construct를 높이면 build 품질과 build 시간이 증가합니다. 검색 시 hnsw_ef를 높이면 recall이 좋아질 수 있지만 query latency도 늘어납니다. Qdrant Indexing, Measuring ANN Recall
| 설정 | 영향과 비용 |
|---|---|
m | 더 촘촘한 그래프와 recall 상한, 대신 RAM, disk, build 시간 증가 |
ef_construct | build 때 더 나은 이웃 선택, 대신 build 시간 증가와 rebuild 필요 |
hnsw_ef | query마다 후보 탐색과 recall 증가 가능, 대신 검색 지연 증가 |
limit | reranker 후보 증가, 대신 payload와 reranker 비용 증가 |
초기에는 Qdrant 기본값을 출발점으로 두고, 같은 query에 exact search를 실행해 ANN Recall@k를 측정합니다. recall 목표를 달성하지 못할 때 먼저 hnsw_ef를 64, 128, 256처럼 비교하고, 그래도 부족할 때 build 설정을 바꿉니다.
3. payload와 원문 조회
ANN이 반환하는 것은 보통 ID와 점수입니다. 제목, URL, 본문을 payload에 함께 두었다면 한 번에 받을 수 있습니다. 별도 object store나 relational DB에 원문을 두면 추가 조회가 필요합니다.
top 1,000개의 큰 본문 payload를 매번 가져오면 벡터 검색이 빨라도 전체 응답은 느립니다. 후보 생성 단계에서는 ID, 점수, 짧은 제목만 받고, rerank할 후보에만 본문을 가져오는 방식도 검토할 수 있습니다.
4. reranking
bi-encoder embedding은 문서 벡터를 미리 계산할 수 있어 빠릅니다. cross-encoder reranker는 질문과 각 후보 문서를 함께 읽으므로 더 정확할 수 있지만 후보 수에 비례해 비용이 늘어납니다.
실전 시작값은 dense 또는 hybrid로 top 20~50을 찾고, reranker가 top 5~10으로 줄이는 방식입니다. corpus가 작고 질문이 단순하면 reranker를 빼는 편이 더 나을 수 있습니다. 품질 향상이 실제로 있는지 최종 답변 평가로 확인해야 합니다.
5. parent와 인접 문맥 확장
최종 child 청크의 parent section이나 앞뒤 청크는 ID 조회로 가져옵니다. 이 단계에서는 같은 문장이 중복되지 않도록 병합하고, LLM에 보낼 전체 token 예산을 지킵니다.
검색 지연시간과 답변 생성 지연시간도 구분해야 합니다. retrieval이 100ms이고 LLM 생성이 4초라면 HNSW를 20ms 줄이는 것보다 생성 모델, context 길이, streaming을 손보는 편이 사용자 체감에 더 큰 영향을 줄 수 있습니다.
임베딩 차원이 크면 성능이 좋아질까
차원은 의미를 구분할 수 있는 표현 용량과 관련 있지만, 차원만 늘린다고 자동으로 좋은 모델이 되지는 않습니다. 학습 데이터, loss, hard negative, 언어 분포, pooling, instruction이 함께 품질을 결정합니다. 잘 학습된 768차원 모델이 맞지 않는 3,072차원 모델보다 나을 수 있습니다.
Google이 공개한 Gemini Embedding 차원별 MTEB 예시에서도 2,048차원과 1,536차원의 점수가 사실상 같고, 768차원도 매우 가까웠습니다. 이는 특정 모델과 benchmark의 결과이지만 “차원이 두 배면 품질도 두 배”가 아니라는 점을 잘 보여줍니다. Gemini Embeddings, flexible embedding sizes
차원이 직접 늘리는 비용
float32 벡터 개, 차원 의 순수 벡터 저장량 하한은 다음과 같습니다.
| 벡터 수 | 768, 1,024, 1,536차원 순서의 저장량 |
|---|---|
| 10,000 | 약 30.7MB, 41.0MB, 61.4MB |
| 100,000 | 약 307MB, 410MB, 614MB |
| 1,000,000 | 약 3.07GB, 4.10GB, 6.14GB |
이 값에는 ID, payload, segment, 복제본, HNSW graph가 포함되지 않습니다. 실제 용량은 더 큽니다. 차원이 커지면 query vector 전송량, 거리 계산량, 메모리 대역폭 사용량도 함께 증가합니다.
차원을 줄이면 embedding 생성도 같은 비율로 빨라질까
대개 그렇지 않습니다. 모델이 3,072차원 출력을 만들도록 전체 Transformer를 통과한 뒤 마지막 표현을 줄이는 구조라면 비싼 encoder forward는 거의 그대로입니다. 줄어드는 부분은 마지막 projection, API 응답 크기, 저장, ANN 거리 계산입니다.
즉 다음을 구분해야 합니다.
- 모델 크기 축소는 인코더 inference를 크게 줄일 수 있습니다.
- 입력 토큰 축소는 attention과 feed-forward 계산을 줄입니다.
- 출력 차원 축소는 주로 저장과 검색을 줄입니다.
Matryoshka 모델만 공식 방식으로 줄인다
일반 embedding은 정보가 모든 차원에 퍼져 있을 수 있습니다. 뒤쪽 값을 임의로 잘라내면 성능을 예측하기 어렵습니다.
Matryoshka Representation Learning은 앞쪽 작은 차원도 독립적으로 유용하도록 여러 크기를 함께 학습합니다. 논문은 representation을 만드는 forward pass와 downstream retrieval 비용을 구분하고, 후자의 비용이 차원과 데이터 크기에 따라 증가한다고 설명합니다. Matryoshka Representation Learning
Qwen3 Embedding, Gemini Embedding 2, OpenAI text-embedding-3처럼 축소를 공식 지원하는 모델은 문서가 안내하는 방법과 정규화를 사용합니다. 지원 여부를 모르는 모델은 벡터를 임의로 자르지 말고 PCA, quantization, 별도 저차원 모델을 평가합니다.
한국어 Wiki의 권장 초기 아키텍처
작은 Wiki, 수천 청크 이하
| 계층 | 권장 시작값 |
|---|---|
| 모델 | API text-embedding-3-small 또는 로컬 Qwen3-Embedding-0.6B |
| 차원 | 768, Qwen은 768과 1,024 비교 |
| 청크 | H2, H3 기반 300~600토큰, 최대 800 |
| 요약 | 긴 페이지만 page summary vector 추가 |
| 검색 | dense exact 또는 벡터 DB 기본 설정, top 10~20 |
| 키워드 | 고유명사, 코드, 약어가 많으면 BM25 병합 |
| reranker | 먼저 생략하고 실패 질의가 확인되면 추가 |
이 규모에서는 HNSW 튜닝보다 평가 질의, heading metadata, query instruction을 먼저 고칩니다.
중간 규모, 수만에서 수십만 청크
| 계층 | 권장 시작값 |
|---|---|
| 차원 | 768 또는 1,024 |
| 저장 | float32 baseline 후 필요하면 scalar, binary quantization 평가 |
| 검색 | HNSW 기본값에서 시작, exact 결과와 ANN Recall@k 비교 |
| 후보 | dense, BM25 각각 top 30~50, RRF 병합 |
| reranker | 병합 후보 30~50개에서 최종 5~10개 |
| 갱신 | content hash 기반 증분 upsert, 삭제 동기화 |
| 배포 | 새 모델은 별도 collection에 전체 구축 후 alias 전환 |
공개와 비공개 Wiki가 섞인 경우
권한 filter는 검색 후에 적용하면 안 됩니다. 먼저 비공개 벡터가 후보와 score에 들어온 뒤 제거하는 구조는 결과 수가 줄고, 구현 실수 시 제목이나 원문이 노출될 수 있습니다.
가장 단순한 방식은 공개와 비공개 collection을 분리하는 것입니다. 하나의 collection을 써야 한다면 모든 point에 강제 ACL metadata를 넣고, 서버가 사용자 입력과 무관하게 pre-filter를 적용해야 합니다. query embedding cache, 검색 로그, reranker 입력에도 비공개 원문이 남을 수 있으므로 데이터 경계를 전체 pipeline에서 확인합니다.
어떤 설정이 좋은지 검증하는 실험
모델과 청크, 차원을 한꺼번에 바꾸면 무엇이 좋아졌는지 알 수 없습니다. 다음 순서가 실용적입니다.
1. 질문과 정답을 만든다
실제 사용자가 물을 법한 질문 50~100개를 준비합니다. 질문마다 정답 문서와 근거 section을 지정합니다.
- 용어 정의
- 숫자와 설정값
- 원문과 다른 표현을 쓴 의미 검색
- 한글과 영어가 섞인 제품명, API, 약어
- 페이지 전체 요약
- 같은 페이지의 여러 절을 비교하는 질문
- 답이 없는 질문
MTEB에는 Korean benchmark가 별도로 있으며 MIRACL, Ko-StrategyQA 외에도 한국어 retrieval task가 포함됩니다. 모델의 일반 한국어 능력을 확인하는 데 유용하지만, 최종 선택은 우리 Wiki 질의로 해야 합니다. MTEB Korean benchmarks
2. 청크부터 비교한다
모델과 차원을 고정하고 다음을 비교합니다.
- 256토큰 고정 분할
- 제목 기반 300~600토큰
- 제목 기반 최대 800토큰
- 페이지 전체 1청크
- page summary와 제목 기반 청크 이중 인덱스
3. 모델과 차원을 비교한다
가장 좋은 두 청크 설정에 대해서만 모델과 차원을 바꿉니다. MRL 모델이라면 256, 512, 768, 최대 차원을 비교합니다.
4. 속도를 단계별로 기록한다
| 구간 | 기록할 값 |
|---|---|
| 인덱싱 | 문서 수, 청크 수, 총 토큰, 초당 토큰, 전체 시간, API 비용, peak memory |
| 질문 임베딩 | p50, p95, cold start, warm latency |
| ANN | p50, p95, exact 대비 Recall@k |
| payload | 가져온 ID 수, bytes, p50, p95 |
| reranker | 후보 수, p50, p95 |
| 전체 retrieval | p50, p95, timeout 비율 |
| 답변 | 근거 정확도, 답변 정확도, 전체 end-to-end latency |
5. 품질 지표를 함께 본다
- Recall@k는 정답 청크가 후보 안에 들어왔는지 봅니다.
- MRR은 첫 번째 정답이 얼마나 위에 있는지 봅니다.
- nDCG는 여러 관련 결과의 순서를 평가합니다.
- answer containment는 정답 문장이 청크 안에 온전히 들어 있는지 봅니다.
- duplicate rate는 같은 문장이 든 청크가 결과를 차지하는 비율입니다.
검색이 정답 section을 찾지 못하면 embedding, 청크, hybrid retrieval을 고칩니다. section은 맞는데 답변에 근거가 부족하면 parent, 인접 확장을 고칩니다. 검색은 맞고 context도 충분한데 답이 틀리면 생성 prompt와 LLM을 봅니다.
최종 판단표
| 관찰한 문제 | 먼저 바꿀 것과 이유 |
|---|---|
| 정답 문장이 경계에서 잘림 | 제목, 문단 경계와 소량 overlap, 큰 모델도 없는 문장은 복구하지 못함 |
| 구체적 사실 대신 일반 주제만 잡힘 | 작은 section과 metadata, 페이지 전체 의미가 세부 신호를 덮고 있음 |
| 넓은 질문에서 조각만 나옴 | page summary와 parent 확장, 작은 청크의 장점 유지 |
| 제품명, 코드, 약어를 놓침 | BM25 또는 sparse 병합, dense만의 약점 보강 |
| 후보에는 정답이 있으나 순위가 낮음 | reranker, 전체 embedding보다 후보 정렬 문제에 가까움 |
| 인덱싱이 느림 | 길이 bucket, batch, 증분 hash, ANN query 튜닝과 다른 병목 |
| 질문마다 느림 | query embedding, API 왕복, reranker를 각각 측정해 병목 확인 |
| 벡터 DB 메모리가 큼 | 차원 축소, quantization, payload 분리로 모델 교체 없이 절감 |
한국어 Wiki의 안전한 기본값은 제목 기반 300~600토큰, 최대 800토큰, overlap 0~10%, 768 또는 1,024차원입니다. 짧은 단일 주제 페이지만 하나의 청크로 유지하고, 긴 페이지에는 요약 벡터와 section 벡터를 함께 둡니다.
모델이 지원하는 최대 길이는 안전한 입력 상한이지 권장 청크 크기가 아닙니다. 큰 모델은 보통 느리지만, 실제 속도는 모델 크기, 입력 길이, batch, hardware, runtime의 조합으로 측정해야 합니다. 출력 차원은 모델 inference보다 저장과 vector search 비용을 더 직접적으로 바꿉니다.
결국 좋은 설정은 모델 소개 페이지가 아니라 우리 Wiki의 질문과 정답으로 만든 평가표에서 결정됩니다.
참고 자료
- OpenAI, Create embeddings
- OpenAI, text-embedding-3-small
- OpenAI, text-embedding-3-large
- Google, Gemini Embeddings
- Qwen, Qwen3-Embedding-0.6B model card
- BAAI, BGE-M3 model card
- Vaswani et al., Attention Is All You Need
- Kusupati et al., Matryoshka Representation Learning
- Günther et al., Late Chunking
- Qdrant, Indexing
- Qdrant, Measuring ANN Recall
- MTEB, Korean benchmarks