coredot.today
한국어 임베딩 모델, 우리 문서로 직접 재봤다 — Qwen3-Embedding 8B·4B·0.6B, BGE-M3, Nomic v2 MoE 실측 비교 (한국어 검색 스택 1편)
블로그로 돌아가기
한국어 임베딩Qwen3-EmbeddingBGE-M3Nomic EmbedOllama벡터 검색RAGnDCG코사인 유사도지시문 임베딩MTEBKURE한국어 검색 스택실측 벤치마크튜토리얼

한국어 임베딩 모델, 우리 문서로 직접 재봤다 — Qwen3-Embedding 8B·4B·0.6B, BGE-M3, Nomic v2 MoE 실측 비교 (한국어 검색 스택 1편)

RAG를 만들 때 가장 먼저 부딪히는 질문이 '임베딩 모델은 뭘 쓰지?'입니다. 이 글은 그 질문에 남의 벤치마크가 아니라 우리 문서로 답합니다. 코어닷투데이 블로그와 뉴스 589건을 코퍼스로, 사람이 일부러 다른 말로 쓴 질문 22개와 로컬 LLM이 바꿔 쓴 질문 45개, 키워드 질문 18개를 만들고, 맥 한 대의 Ollama에서 Qwen3-Embedding 8B·4B·0.6B, BGE-M3, Nomic Embed v2 MoE 다섯 모델을 같은 조건으로 돌렸습니다. 임베딩이 무엇인지, 코사인 유사도가 왜 각도인지부터 시작해, '청년 일자리'라는 말이 없는 문서를 각 모델이 어떻게 찾는지 실제 값으로 보여 주고, 지시문 접두어 한 줄이 점수를 어떻게 바꾸는지, 같은 한국어 문서를 토크나이저가 얼마나 다르게 쪼개는지, 8B와 4B의 차이가 어디서 나는지를 따져 봅니다. 결론은 순위표 하나가 아니라 '어떤 환경에서 무엇을 고르나'이고, 인터랙티브 6개와 삽화 8장으로 함께 읽습니다. 2편은 BM25·RRF·리랭커, 3편은 마트료시카 임베딩입니다.

코어닷투데이2026-10-0132분

한국어 임베딩, 우리 문서로 재봤다 — 크기가 다른 다섯 로봇이 한국어 문서 벽 앞에서 자를 들고 서 있다크게 보기

들어가며: 남의 순위표 말고, 우리 문서로

RAG 시스템을 만들 때 첫 번째 결정이 임베딩 모델입니다. 인터넷에는 순위표가 넘칩니다. MTEB 리더보드, 한국어 KURE 벤치마크, 각 회사의 모델 카드. 그런데 그 순위표의 문서는 위키피디아이거나 논문이거나 영어이고, 여러분의 문서는 공고문이고 회의록이고 보도자료입니다. 순위표는 방향을 알려 주지만 답을 알려 주지는 않습니다.

그래서 이 글은 순서를 바꿉니다. 먼저 우리 문서로 잽니다. 코어닷투데이 블로그 글 525건과 뉴스 64건, 합쳐 589건의 제목과 요약(평균 242자)을 코퍼스로 삼았습니다. 질문은 세 종류로 만들었습니다.

질문 유형개수만든 방법예시
사람 바꿔쓰기22글쓴이가 정답 문서의 핵심어를 일부러 피해서 씀. 정답이 여럿인 질문 5개 포함"똑같은 LLM인데 주변 장치만 바꿨더니 점수가 크게 달라진 실험" → 정답: 하니스 해부 글
LLM 바꿔쓰기45로컬 27B 모델(qwen3.8)에 문서를 주고 "핵심 명사를 베끼지 말고" 질문을 만들게 함. 카테고리별 층화 표집"행과 열 배치 방식에 따라 조회 속도가 달라지는 원리" → 정답: 컬럼형 DB 글
키워드18제목의 고유명사를 그대로 씀"Headlamp Freelens k9s 비교"

모델은 로컬 맥(M1 Ultra, 128GB)의 Ollama에 올라 있는 다섯 개입니다. 지표는 검색 벤치마크의 표준인 nDCG@10(상위 10개 안에 정답이 얼마나 위에 있나)을 중심으로, MRR과 Recall@5·10을 함께 봅니다.

이 글은 3부작의 1편입니다. 1편은 임베딩 모델 자체를, 2편은 BM25와 벡터를 합치는 RRF와 리랭커를, 3편은 벡터를 잘라 쓰는 마트료시카 임베딩을 다룹니다. 세 편 모두 같은 코퍼스와 같은 질문으로 잰 실측을 씁니다.


1부. 임베딩이란 무엇인가, 3분 복습

문장을 숫자 줄로

임베딩 모델은 문장을 받아 숫자 한 줄(벡터)로 바꿉니다. Qwen3-Embedding-8B는 4,096개, BGE-M3는 1,024개, Nomic v2는 768개의 숫자를 냅니다. 이 숫자 줄의 약속은 하나입니다. 뜻이 비슷한 문장은 가까운 벡터가 된다.

문장이 벡터가 된다 — 기계에 들어간 문장 카드가 색칠된 네모 줄로 나오고, 비슷한 문장의 줄은 지도 위에서 가깝다크게 보기

'가깝다'를 재는 자가 코사인 유사도 입니다. 두 벡터를 원점에서 뻗은 화살표로 보고, 그 사이 각도의 코사인을 취합니다. 같은 방향이면 1, 직각이면 0, 반대면 −1. 길이는 무시하고 방향만 봅니다. 그래서 짧은 질문과 긴 문서도 비교할 수 있습니다.

코사인 유사도는 각도다 — 가까운 두 화살표 사이의 작은 각과 멀리 떨어진 화살표의 큰 각크게 보기

실제 값으로 보기

원문 글(이 시리즈의 출발점이 된 임베딩 모델 추천 메모)에 좋은 예시가 있었습니다. 질문에는 '청년 일자리'가 있는데, 정답 문서에는 '청년취업 지원사업'이라고만 적혀 있는 경우. 단어가 겹치지 않아도 뜻으로 찾아야 합니다. 다섯 모델에 그 질문과 문서 다섯 개를 넣고 코사인을 그대로 찍었습니다.

여기서 두 가지가 보입니다. 첫째, 다섯 모델 모두 도커 문서와 키오스크 문서를 맨 아래로 보냅니다. 뜻이 다르면 멀다는 약속은 지켜집니다. 둘째, '청년취업'과 '청년창업'의 순서는 모델마다 다르고, 대부분 창업 공고를 더 가깝다고 봅니다. 질문의 '사업'이라는 말이 '지원사업 공고'와 끌리는 것입니다. 취업과 창업은 다른 뜻인데 표면이 비슷합니다. 임베딩 하나로 검색을 끝내면 안 되는 이유가 여기 있고, 2편의 주제입니다.

절댓값에 속으면 안 됩니다. BGE-M3는 관계없는 문서에도 0.2~0.3을 주고, Nomic은 0.06을 줍니다. 모델마다 코사인의 '기본 온도'가 다르므로, 모델을 바꾸면 임계값도 다시 잡아야 합니다.


2부. 다섯 모델 소개

모델계보설계의 특징한국어 관점의 메모
Qwen3-Embedding 8B / 4B / 0.6B
알리바바, 2025-06 (arXiv:2506.05176)
Qwen3 LLM을 기반으로 한 디코더형 임베딩. 대규모 비지도 사전학습 → 지도 미세조정 → 모델 병합. 학습 데이터의 상당 부분을 Qwen3 LLM이 합성지시문 인식(instruction-aware): 질의 앞에 "Instruct: … Query: "를 붙이면 과제에 맞게 벡터가 바뀜. 32~4096차원 마트료시카. 32K 컨텍스트MTEB 다국어 70.58(8B)·69.45(4B)·64.33(0.6B). KURE 한국어 검색 9과제 평균 nDCG@10은 8B 0.783, 4B 0.774
BGE-M3
BAAI, 2024-02 (arXiv:2402.03216)
XLM-RoBERTa 계열 인코더. 밀집·희소·다중벡터 세 기능을 자기지식증류로 한 모델에 담음8,192토큰. 100+개 언어. Ollama에서는 밀집 벡터만 나오므로 희소·다중벡터를 쓰려면 FlagEmbedding으로 직접 서빙KURE 9과제 평균 0.751. MrTidy 과제에서는 1위(0.647). 2년 된 모델인데 여전히 강함
Nomic Embed v2 MoE
Nomic AI, 2025-02 (arXiv:2502.07972)
최초의 범용 MoE 텍스트 임베딩. 총 475M, 추론 시 305M 활성(전문가 8, top-2)768→256 마트료시카. 질의에 search_query:, 문서에 search_document: 접두어 필수. 컨텍스트 512토큰BEIR 52.86, MIRACL 65.80. 한국어 단독 수치는 공개 안 됨

원문 메모가 인용한 "Qwen3-Embedding-8B 한국어 nDCG 0.751, BGE-M3 0.713, 0.6B 0.687"이라는 수치는 이 글에서 출처를 확인하지 못했습니다. 대신 이 블로그의 RAG 특집 7편에서 검증한 KURE 저장소의 MTEB(kor) 9과제 평균을 씁니다. 8B 0.783, 4B 0.774, BGE-M3 0.751. 순서는 같고 절댓값은 다릅니다.


3부. 실측 결과

순위표

결과를 표로 정리하면 다음과 같습니다(각 모델의 최선 설정).

nDCG@10 — 85개 질문(사람 22 + LLM 45 + 키워드 18), 문서 589건 (최댓값 0.889 기준)
Qwen3-Embedding-8B (지시문)
0.889
Qwen3-Embedding-4B (지시문)
0.854
BGE-M3
0.833
Nomic Embed v2 MoE (접두어)
0.805
Qwen3-Embedding-0.6B
0.801
BM25 (Kiwi 형태소)
0.616

키워드 질문 18개는 BM25조차 0.98을 받을 만큼 쉬워서 모델 간 차이를 지우므로, 아래 분석은 어휘가 일부러 어긋난 67개(사람 22 + LLM 45)를 기준으로 합니다. 그 67개에서는 8B 0.860, 4B 0.815, BGE-M3 0.788, Nomic 0.758, 0.6B 0.748, BM25 0.519였습니다.

8B와 4B의 차이는 어디서 나나

가장 궁금한 비교입니다. 67개 질문에서 8B는 4B보다 nDCG@10이 0.045 높았습니다. 어디서 벌어졌을까요.

지표 (67개 질문)Qwen3-8BQwen3-4B차이읽는 법
nDCG@100.8600.815+0.045종합
MRR0.8390.789+0.050첫 정답의 등수
정답이 1등인 비율74.6%70.1%+4.5%p67개 중 3개 차이
Recall@50.9230.878+0.0455등 안에 넣는 힘
Recall@100.9530.930+0.02310등 안에는 둘 다 잘 넣음
사람 바꿔쓰기 22개 nDCG0.821—어려운 질문에서 낮아짐
LLM 바꿔쓰기 45개 nDCG0.878—

차이는 '10등 안에 넣느냐'가 아니라 '5등 안, 1등에 올리느냐'에서 납니다. 둘 다 정답을 상위권에 두지만, 8B가 더 자주 맨 위에 둡니다. 실무적 의미는 이렇습니다. 상위 5개를 LLM에 그대로 넘기는 단순한 RAG라면 8B의 이점이 답변 품질로 이어지고, 2편처럼 상위 20개를 리랭커로 다시 고르는 구조라면 4B로도 거의 같은 자리에 갑니다.

크면 정확하고, 작으면 빠르다 — 문서 더미를 나르는 네 로봇의 이어달리기크게 보기

속도와 크기

문서 589건을 임베딩하는 데 BGE-M3는 22초, Nomic은 32초, 0.6B는 36초가 걸렸고, 4B는 170초, 8B는 295초가 걸렸습니다. 4B와 8B는 같은 시간에 로컬 LLM이 질문을 만들고 있어 GPU를 나눠 썼으므로 느리게 잰 값입니다. 4B만 나중에 단독으로 다시 재니 121초(4.85건/초)로 빨라졌고, 8B는 재측정 중에 Ollama가 다른 작업으로 바빠져 끝내지 못했습니다(8부). 그래도 방향은 분명합니다. 4B는 8B의 두 배 가까이 빠르고, BGE-M3는 4B의 몇 배 빠릅니다. 문서 수천만 건을 처음 인제스천할 때 이 배수가 며칠의 차이가 됩니다.

질의 한 건의 임베딩은 Nomic 23ms, 0.6B 33ms, BGE-M3 58ms, 4B 109ms, 8B 117ms였습니다. 검색 한 번의 지연 예산이 200ms라면 8B도 들어가지만, 절반 이상을 임베딩이 먹습니다.


4부. 지시문 한 줄의 효과

Qwen3-Embedding은 질의 앞에 지시문을 붙이라고 안내합니다. 모델 카드의 표현으로 "대부분의 과제에서 1~5% 개선". 우리 데이터로 재 봤습니다.

지시문 접두어 — 같은 질문 카드에 '검색 질문입니다' 쪽지를 붙이자 로봇의 눈에 표적이 켜진다크게 보기

모델지시문 없음지시문 붙임차이 (nDCG@10, 67개)
Qwen3-8B0.8450.860+0.015
Qwen3-4B0.8130.815+0.002
Qwen3-0.6B0.7480.728−0.020
Nomic v2 (search_query: 접두어)0.7490.758+0.009

8B에서는 카드의 안내대로 이득이 있고, 4B에서는 거의 없고, 0.6B에서는 오히려 손해 였습니다. 작은 모델은 영어 지시문을 소화할 여유가 적어 보입니다. 지시문은 "Given a web search query, retrieve relevant passages that answer the query"라는 모델 카드의 기본 문장을 그대로 썼는데, 한국어 지시문이나 도메인 지시문("뉴스 기사를 찾는 질문")으로 바꾸면 결과가 달라질 수 있습니다. 확실한 것은 하나입니다. 지시문 유무는 반드시 여러분의 데이터로 A/B 해 보고 정해야 합니다. 그리고 문서와 질의를 다른 설정으로 임베딩하면 안 됩니다. 이 실험에서도 문서는 지시문 없이, 질의만 지시문을 붙였습니다(Qwen3의 권장 방식).

Nomic은 접두어가 선택이 아니라 필수입니다. 모델 카드가 "task instruction prefix가 반드시 포함돼야 한다"고 명시하는데, 우리 실측에서는 붙였을 때 +0.009였습니다. 크지 않지만 방향은 맞습니다.


5부. 같은 한국어 문서, 다른 토큰 수

실험 중에 우연히 눈에 띈 숫자가 있습니다. 같은 589건을 임베딩할 때 Ollama가 보고한 처리 토큰 수입니다.

같은 뜻, 더 많은 조각 — 한국어 문장은 형태소와 조사로 잘게 나뉘고, 영어 문장은 몇 조각으로 끝난다크게 보기

문서 589건 임베딩에 쓰인 토큰 수 (최댓값 95,501 기준)
Qwen3-Embedding (Qwen 토크나이저)
95,501
BGE-M3 (XLM-R 토크나이저)
75,348
Nomic v2 MoE (XLM-R 토크나이저)
75,348

Qwen 토크나이저는 같은 한국어를 XLM-RoBERTa 계열보다 27% 더 많은 토큰 으로 쪼갭니다. 이것이 뜻하는 것은 세 가지입니다. 첫째, Qwen3 계열의 처리량 차이 일부는 모델 크기가 아니라 토큰 수에서 옵니다. 둘째, 컨텍스트 한도를 토큰으로 셀 때 Qwen3의 32K는 한국어로는 XLM-R 계열의 25K 정도에 해당합니다(그래도 BGE-M3의 8K, Nomic의 512보다 훨씬 깁니다). 셋째, 청킹 크기를 정할 때 '토큰 512개'가 모델마다 다른 글자 수라는 것입니다. Nomic의 512토큰 한도는 한국어 요약 한 건(평균 242자)에는 충분했지만, 회의록 한 페이지에는 모자랍니다.


6부. 질문별로 보면 다른 그림

평균 점수는 순위를 정하지만, 무엇이 어려운지는 말해 주지 않습니다. 질문 하나하나를 히트맵으로 펼쳐 보세요.

몇 가지 패턴이 있습니다.

  • 8B와 BM25가 함께 놓친 질문이 넷, 다섯 임베딩 모델이 전부 놓친 질문이 하나 있습니다. "AI가 짜 준 코드를 사람이 미리 승인하는 단계가 필요 없어졌다는 주장"(정답: 플랜 모드는 죽었다)처럼 정답 문서의 제목과 요약이 질문과 다른 층위의 말로 쓰인 경우입니다. 임베딩은 '플랜 모드'를 '승인 단계'로 번역해 주지 않습니다.
  • BM25가 벡터를 이긴 질문이 셋 있습니다. "보상 모델 없이 언어 모델을 인간 취향에 맞추는 수학적 접근법"에서 BM25는 정답(DPO 글)을 1등에 두고 8B는 3등에 뒀습니다. '보상 모델'이라는 정확한 용어가 문서에 그대로 있었기 때문입니다. 2편에서 이 셋을 다시 만납니다.
  • 정답이 여럿인 질문 (git 시리즈 3편, 토닥북 뉴스 3건)에서는 모든 모델이 하나는 찾고 나머지를 놓쳤습니다. 시리즈 글의 요약이 서로 비슷해 상위권이 그 시리즈로 채워지는 대신, 정답으로 표시한 특정 편이 밀린 것입니다.

순위표 — 큰 주황 로봇이 1등, 중간 로봇이 2등, 태극기를 단 초록 로봇이 3등크게 보기


7부. 그래서 무엇을 고르나

원문 메모의 추천 순서는 "Qwen3-Embedding 4B/8B → BGE-M3 → Nomic v2 MoE"였습니다. 우리 실측은 그 순서를 대체로 확인하되, 단서를 붙입니다.

①
품질만 보면 8B, 그런데 차이는 '1등에 올리는 힘'
8B와 4B의 nDCG 차이 0.045는 Recall@10이 아니라 Recall@5와 MRR에서 났습니다. 리랭커를 둘 계획이면 4B로 충분하고, 상위 몇 개를 그대로 LLM에 넘기는 구조면 8B의 값어치가 있습니다.
②
BGE-M3는 '크기 대비' 여전히 최고
567M으로 4B의 0.97배, 처리량은 8배. CPU 서버나 GPU가 빠듯한 환경이라면 여기서 시작하는 것이 맞습니다. 8K 컨텍스트라 긴 문서에도 강합니다. 단, Ollama로는 밀집 벡터만 나옵니다.
③
Nomic v2는 '저장 비용'이 병목일 때
품질은 넷 중 넷째지만, 활성 305M으로 가볍고 256차원으로 줄여도 쓸 만합니다(3편). 수천만 벡터에서 저장 비용이 모델 비용을 넘어설 때 후보가 됩니다. 컨텍스트 512토큰은 반드시 청킹으로 보완해야 합니다.

용도에 맞는 크기를 고른다 — 로켓형·저울형·깃털형·수축형 상자를 비교하는 엔지니어크게 보기

그리고 이 글의 결론 중 가장 중요한 것은 모델 이름이 아닙니다. 모델 하나로 검색을 끝내지 마세요. 67개 어려운 질문에서 가장 좋은 8B도 4개를 완전히 놓쳤고, BM25가 이긴 질문이 셋 있었고, 취업과 창업을 헷갈렸습니다. 2편에서는 BM25와 벡터를 합치는 RRF가 언제 돕고 언제 해치는지를 같은 데이터로 재고, 리랭커가 그 빈틈을 어떻게 메우는지 봅니다.


8부. 이 실험의 한계

  • 코퍼스가 작습니다. 589건이면 어떤 모델도 정답을 10등 안에 넣기 쉽습니다. 실제 수십만 건에서는 Recall이 전반적으로 내려가고 모델 간 격차가 벌어질 수 있습니다.
  • 질문이 편향돼 있습니다. 사람 질문 22개는 글쓴이가 썼고, LLM 질문 45개는 한 모델이 만들었습니다. 두 유형 모두 '어휘가 어긋나는' 질문을 의도해서, 벡터 검색에 유리하고 BM25에 불리합니다. 키워드 질문 18개는 반대로 너무 쉬웠습니다.
  • 정답이 하나라는 가정 이 거칩니다. 시리즈 글처럼 정답이 여럿인데 하나만 표시한 경우가 섞여 있습니다.
  • 처리량 측정에 간섭이 있었습니다. 4B와 8B의 문서 임베딩은 로컬 LLM이 동시에 돌던 시간에 잰 값입니다. 4B는 단독 재측정(121초)으로 보정했지만 8B는 보정하지 못했으므로, 8B의 2.0건/초는 하한으로 읽어야 합니다. 질의 지연은 단독 실행 값입니다.
  • 양자화된 모델 입니다. Qwen3 세 모델은 Q4_K_M, BGE-M3와 Nomic은 F16입니다. 원본 가중치와 결과가 다를 수 있습니다.
  • Ollama 한계. BGE-M3의 희소·다중벡터 기능은 쓰지 못했습니다. 그 기능을 살린 BGE-M3는 이 글의 수치보다 좋을 것입니다.

그래서 이 글의 마지막 권고는 원문 메모와 같습니다. 여러분의 문서로 질문 200~500개를 만들어 직접 재세요. 이 글의 스크립트는 그대로 쓸 수 있고, 하루면 됩니다.


부록: 실험 재현 방법

Ollama 임베딩 호출 (Python, 표준 라이브러리만)
python
import json, urllib.request

def embed(model, texts, dimensions=None):
    body = {"model": model, "input": texts, "keep_alive": "30m"}
    if dimensions:
        body["dimensions"] = dimensions  # 마트료시카 모델만 (3편)
    req = urllib.request.Request(
        "http://localhost:11434/api/embed",
        data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json"},
    )
    return json.load(urllib.request.urlopen(req))["embeddings"]

# Qwen3: 질의에만 지시문, 문서는 그대로
INSTRUCT = ("Instruct: Given a web search query, retrieve relevant passages "
            "that answer the query\nQuery: ")
q = embed("qwen3-embedding:4b", [INSTRUCT + "울산 청년 일자리 사업"])
d = embed("qwen3-embedding:4b", ["지역주도형 청년취업 지원사업 추진"])

# Nomic: 양쪽 모두 접두어 필수
q = embed("nomic-embed-text-v2-moe", ["search_query: 울산 청년 일자리 사업"])
d = embed("nomic-embed-text-v2-moe", ["search_document: 지역주도형 청년취업 지원사업 추진"])

평가는 nDCG@10, MRR, Recall@k를 직접 구현했고(수십 줄), BM25는 rank_bm25에 Kiwi 형태소 분석기(명사·동사·외국어·숫자만 남김)를 붙였습니다. 벡터는 정규화한 뒤 행렬곱 한 번으로 모든 질문·문서 쌍의 코사인을 구합니다. 589 × 85 쌍이면 노트북에서 1초도 안 걸립니다.


출처

모델

한국어 벤치마크

도구

이 시리즈

함께 읽으면 좋은 코어닷투데이 글