RAG와 임베딩 · 글 11편
RAG와 임베딩이 글에서 다루는 것내 PC에서 돌릴 수 있는 임베딩 모델의 예시와 고르는 기준을 정리합니다. 한국어 문서에서 확인할 점, 접두어 규칙, 벡터 차원을 줄이는 방법을 다룹니다.
실행하는 방법은 두 가지
로컬에서 임베딩 모델을 돌리는 길은 크게 둘입니다.
| 방법 | 설명 | 맞는 경우 |
|---|---|---|
| Ollama | 대화 모델과 같은 방식으로 받아서 API로 호출합니다 | 이미 Ollama를 쓰고 있고, 설치를 늘리고 싶지 않을 때 |
| sentence-transformers | 파이썬 라이브러리로 Hugging Face의 모델을 직접 불러옵니다 | 파이썬 코드로 색인을 만들 때, 이미지까지 다룰 때 |
Ollama 쪽은 명령 한 줄로 받습니다.
ollama pull nomic-embed-text
받은 뒤에는 Ollama의 임베딩 API로 호출합니다.
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "연차 며칠 쓸 수 있어?"
}'
응답의 embeddings 항목에 숫자 목록이 들어 있습니다. Ollama 설치와 기본 명령은 Ollama 처음 시작하기에 있습니다.
sentence-transformers 쪽은 파이썬에서 이렇게 씁니다.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("intfloat/multilingual-e5-small")
vecs = model.encode(["passage: 휴가는 연 15일을 기본으로 한다."])
print(vecs.shape)
처음 실행할 때 모델 파일을 내려받습니다. 인터넷이 안 되는 PC에서 쓰려면 모델 폴더를 미리 받아 옮겨 둡니다.
강의에서 쓰는 모델 예시
아래는 신성진 강사의 강의 자료에 등장하는 모델입니다. 강의 시점 기준의 예시이고, 순위가 아닙니다. 새 모델은 계속 나오므로 받기 전에 모델 카드를 확인합니다.
| 모델 예시 | 받는 곳 | 강의에서 쓴 용도 |
|---|---|---|
| nomic-embed-text | Ollama | 내부망용 RAG 실습의 기본 임베딩 모델 |
| bge-m3 | Ollama | 위 모델의 대안으로 함께 안내 |
| multilingual-e5-small | Hugging Face | 기관 문서 검색 실습. 파이썬과 NumPy로 유사도 계산 |
| EmbeddingGemma 계열 | Ollama, Hugging Face | 규정 검색, 사례 검색, 사진 의미 검색 실습 |
Ollama에 올라온 임베딩 모델은 Ollama 임베딩 모델 목록에서 볼 수 있습니다. 이 사이트의 모델 찾기에도 관련 링크를 모아 두었습니다.
고르는 기준
1. 한국어를 지원하는가
가장 먼저 확인할 항목입니다. 임베딩 모델 가운데에는 영어 자료를 중심으로 만든 것이 있습니다. 이런 모델에 한국어 문서를 넣으면 숫자는 나오지만 검색 품질이 떨어질 수 있습니다.
- 모델 카드에서 지원 언어를 확인합니다. 이름에 multilingual이 들어 있거나 다국어 지원을 밝힌 모델을 우선 봅니다.
- 강의의 기관 문서 실습이 다국어 모델(multilingual-e5-small)을 쓰는 이유가 이것입니다.
- 모델 카드보다 확실한 것은 내 문서로 해 보는 것입니다. 질문 10개와 기대하는 문서를 적어 두고, 후보 모델마다 상위 결과에 그 문서가 나오는지 셉니다. 방법은 RAG 답변 평가와 자주 생기는 실패에 있습니다.
2. 내 PC에서 감당할 수 있는가
임베딩 모델은 대화 모델보다 작아서 그래픽카드 없이도 돌아갑니다. 실습 프로젝트도 CPU만으로 실행하도록 만들어져 있습니다. 다만 문서가 많으면 처음 색인하는 데 시간이 걸립니다. 이름에 small이 붙은 작은 모델로 시작하고, 품질이 부족할 때 큰 모델로 바꿉니다.
3. 한 번에 읽을 수 있는 길이
임베딩 모델마다 입력 길이 제한이 있습니다. 제한보다 긴 조각을 넣으면 뒷부분이 잘려 나가도 오류가 나지 않는 경우가 많습니다. 잘린 뒷부분은 검색되지 않습니다. 청크 크기를 모델의 제한 안쪽으로 맞춥니다.
4. 벡터 차원
차원은 벡터 하나에 들어 있는 숫자의 개수입니다. 차원이 크면 저장 공간과 계산량이 늘어납니다. 문서 수백 건 규모에서는 차이를 느끼기 어렵고, 수만 건 이상일 때 의미가 생깁니다.
5. 텍스트만인가, 이미지도 되는가
사진, 도면, 스캔 이미지를 글로 찾고 싶다면 이미지를 입력으로 받는 모델이 필요합니다. 텍스트 전용 모델로는 할 수 없습니다.
접두어와 프롬프트 이름
일부 모델은 문서와 질문을 구분해서 넣도록 만들어져 있습니다. 규칙을 지키지 않으면 오류 없이 품질만 떨어지므로 알아채기 어렵습니다.
multilingual-e5 계열은 글 앞에 접두어를 붙입니다.
doc_vecs = model.encode(["passage: " + c["text"] for c in chunks])
q_vec = model.encode("query: 연차 며칠 쓸 수 있어?")
문서에는 passage: , 질문에는 query: 를 붙입니다. 강의에서 코딩 에이전트에게 작업을 맡길 때도 이 규칙을 요청문에 직접 적습니다.
각 조각에 문서 ID와 원문 위치를 남기세요. multilingual-e5-small 모델에 문서는 passage: 접두어, 질문은 query: 접두어를 붙여 임베딩하고 상위 근거를 반환하세요. 답변에 근거가 없으면 모른다고 표시하세요.
EmbeddingGemma 계열은 접두어 대신 프롬프트 이름을 지정합니다. 실습 프로젝트의 검색 코드는 질문을 이렇게 임베딩합니다.
query_emb = model.encode(req.query, prompt_name="SearchQuery")
모델마다 규칙이 다르므로 모델 카드의 사용 예시를 그대로 따릅니다.
차원을 줄여 쓰는 모델
일부 모델은 벡터의 앞부분만 잘라 써도 검색이 되도록 학습되어 있습니다. 이 방식을 마트료시카 표현 학습(MRL)이라고 합니다. 실습 프로젝트는 768차원 벡터를 128, 256, 512차원으로 잘라 쓰는 기능을 넣어 두었습니다.
def get_truncated_vector(vec_full, dim: int):
v = np.array(vec_full[:dim], dtype=np.float32)
norm = np.linalg.norm(v)
if norm > 0:
v = v / norm
return v
앞에서 dim개만 잘라낸 뒤 길이를 다시 1로 맞춥니다. 자른 뒤 정규화를 다시 하는 점이 중요합니다. 숫자 하나를 4바이트로 저장하면 768차원은 벡터 하나에 3,072바이트, 128차원은 512바이트입니다.
차원을 줄이면 순위가 조금 달라질 수 있습니다. 실습 프로젝트는 같은 질문으로 768차원의 상위 5개와 줄인 차원의 상위 5개가 얼마나 겹치는지 비교하는 기능을 함께 두었습니다. 줄여도 되는지는 내 자료로 이 비교를 해 보고 정합니다. 이 방식을 지원하지 않는 모델의 벡터를 잘라 쓰면 안 됩니다.
모델을 바꿀 때
임베딩 모델을 바꾸면 기존 벡터는 쓸 수 없습니다. 문서 전체를 새 모델로 다시 임베딩합니다. 그래서 색인을 만들 때 다음을 함께 기록해 둡니다.
- 모델 이름과 버전
- 벡터 차원
- 정규화 여부
- 접두어 또는 프롬프트 이름
강의의 실습 완료 기준도 “모델 ID와 벡터 차원이 일치하고, 상위 조각에서 원문을 열 수 있어야 한다”입니다. 이 기록이 없으면 몇 달 뒤 문서를 추가할 때 어떤 설정으로 만든 색인인지 알 수 없습니다.
자주 하는 실수
- 문서는 A 모델로, 질문은 B 모델로 임베딩합니다. 결과가 무작위에 가깝게 나옵니다.
- 접두어를 문서에만 붙이고 질문에는 빼먹습니다.
- 모델을 바꾼 뒤 새 문서만 새 모델로 추가합니다. 한 색인에 두 종류의 벡터가 섞입니다.
- 대화 모델의 이름을 임베딩 API에 넣습니다. 임베딩 전용 모델을 따로 받아야 합니다.