사내 AI 활용 가이드

문서가 벡터가 되기까지
청킹과 임베딩

청킹 임베딩 벡터
연구소 기술노트 · 2026년 8월 · 읽는 데 약 15분
2/5 시리즈 사내 문서 AI · 기초 — 심화

이 글의 목표 — ① 문서를 왜 조각내야 하는지 ② 어떻게 잘라야 잘 자른 것인지 ③ 글자가 숫자로 바뀌는 과정 ④ 컴퓨터가 "의미가 비슷하다"를 계산으로 판단하는 방법을 이해합니다.

1편에서 RAG의 준비 단계를 이렇게 정리했습니다.

문서 → 조각내기 → 숫자로 변환 → 데이터베이스 저장
       (청킹)      (임베딩)

이번 편은 앞의 두 작업을 다룹니다. 이 두 단계가 RAG 품질의 대부분을 결정하기 때문에, 시리즈에서 가장 중요한 내용이라고 해도 지나치지 않습니다.

1. 왜 문서를 조각내야 하는가

문서를 검색에 쓸 수 있는 작은 단위로 나누는 작업을 청킹이라고 합니다. 잘라 놓은 조각 하나를 청크라고 부릅니다.

50쪽짜리 매뉴얼을 통째로 하나의 단위로 다루면 안 되는 이유는 세 가지입니다.

첫째, 검색이 부정확해집니다. 50쪽 전체를 하나로 뭉뚱그리면 "이 문서는 매뉴얼입니다" 정도의 뭉툭한 성격만 남습니다. 안에 담긴 신청 절차, 수수료, 반출 규정 같은 세부 내용이 서로 희석되어 버립니다.

둘째, 필요한 부분만 꺼낼 수 없습니다. 수수료를 물었는데 50쪽 전체를 가져오면 LLM이 그 안에서 다시 찾아야 합니다. 처음부터 수수료 부분만 정확히 꺼내 오는 편이 훨씬 낫습니다.

셋째, 분량 제한이 있습니다. LLM이 한 번에 읽을 수 있는 양에는 한계가 있습니다. 문서 몇 개만 넣어도 금방 한도를 넘깁니다.

주의 — 검색이 잘 안 될 때 대부분의 사람들이 모델부터 바꾸려고 합니다. 그러나 실제 원인은 대개 청크가 너무 크거나 엉뚱한 지점에서 잘린 것입니다. 청킹은 가장 큰 영향을 주면서도 가장 소홀히 다뤄지는 단계입니다.

2. 재귀적 청킹 — 가장 널리 쓰이는 방법

단순하게 생각하면 "500자마다 자르기"가 떠오릅니다. 실제로 그런 방식도 있고 고정 크기 청킹이라고 부릅니다. 그런데 이렇게 자르면 문장 한가운데나 심지어 단어 중간에서 끊깁니다.

"3분기 매출은 1,250억|원으로 전년 대비..."
                     ↑ 여기서 잘림

그래서 실무에서는 재귀적 청킹을 씁니다. 이름은 어렵게 들리지만 원리는 간단합니다. 구분자를 우선순위대로 시도하되, 조각이 아직 크면 더 작은 구분자로 다시 자르는 방식입니다.

순위구분자의미
1빈 줄문단 단위로 자릅니다
2줄바꿈문장 단위로 자릅니다
3공백단어 단위로 자릅니다
4없음마지막 수단으로 글자 단위로 자릅니다

실제로 어떻게 동작하는가

최대 크기를 100자로 정했다고 가정하고 따라가 보겠습니다.

  1. 먼저 빈 줄로 자릅니다. 문단 A가 45자, B가 65자, C가 55자로 나왔습니다. 모두 100자 이하이므로 여기서 끝납니다.
  2. 만약 문단 B가 200자였다면, B만 줄바꿈 기준으로 다시 자릅니다. A와 C는 건드리지 않습니다.
  3. 그래도 한 줄이 100자를 넘으면 그 줄만 공백 기준으로 자릅니다.

핵심은 문제가 되는 조각만 더 잘게 나눈다는 점입니다. 이미 적당한 크기인 부분은 그대로 둡니다. 그래서 결과가 자연스럽고, 대부분의 조각이 문단이나 문장 경계에서 깔끔하게 끊깁니다.

오버랩 — 경계에서 맥락이 끊기지 않게

아무리 잘 잘라도 조각과 조각 사이에서는 흐름이 끊깁니다. 앞 조각의 마지막 부분을 다음 조각 앞에 조금 겹쳐 두면 이 문제가 완화됩니다. 이것을 오버랩이라고 합니다.

조각 1: "...신청서를 제출합니다. 심의는 7일이 걸립니다."
조각 2: "심의는 7일이 걸립니다. 승인 후 수수료를 납부합니다..."
         └─────── 겹치는 부분 ───────┘

분량은 보통 조각 크기의 10~20%로 잡습니다. 500자로 자른다면 50~100자 정도를 겹칩니다.

3. 어떤 방식을 골라야 하는가

널리 쓰이는 방식은 세 가지입니다.

방식장점단점
고정 크기 구현이 단순하고 빠릅니다. 조각 크기를 예측할 수 있습니다 문장이나 단어 중간에서 잘립니다
재귀적 의미 경계에서 잘리고 설정이 간단합니다 내용이 바뀌는 지점을 완벽히 감지하지는 못합니다
시맨틱 주제가 바뀌는 지점을 정확히 찾아 가장 자연스럽게 나눕니다 준비 과정이 무거워 느리고 비용이 큽니다

시맨틱 청킹은 문장들을 미리 숫자로 바꿔 보고, 앞뒤 문장의 성격이 급격히 달라지는 지점을 주제 전환으로 판단해 자릅니다. 정확하지만 문서 한 건마다 계산을 여러 번 돌려야 합니다.

상황권장 방식이유
처음 만들어 보는 경우재귀적설정 두 줄로 충분한 품질이 나옵니다
사내 매뉴얼·규정재귀적제목과 문단 구조가 있어 잘 맞습니다
뉴스·논문 등 긴 글시맨틱주제 전환이 문단과 일치하지 않습니다
코드 문서고정 크기 + 별도 구분자함수 단위로 자르는 편이 낫습니다
표 형태 데이터행 단위행 하나가 곧 의미 단위입니다

문서 종류에 따라 단위를 달리합니다

같은 재귀적 청킹을 쓰더라도, 문서 성격에 맞춰 크기와 기준을 조정해야 합니다. 실제 사례를 보시면 감이 잡히실 것입니다.

문서 유형청크 단위이유
이용 매뉴얼절차 단위
(1단계~N단계를 한 조각으로)
단계가 흩어지면 순서 정보가 사라집니다
조사 메타정보조사명 + 항목 목록 전체한 조사의 정보가 통째로 검색되어야 합니다
운영 규정조항 단위"제 몇 조" 단위로 인용되므로 쪼개면 안 됩니다

여기까지가 청킹입니다. 이제 잘라 놓은 조각을 컴퓨터가 다룰 수 있는 형태로 바꿀 차례입니다.

4. 벡터 — 의미를 좌표로 나타내기

컴퓨터는 숫자만 다룹니다. "의미가 비슷한 문서를 찾아라"라는 요구를 컴퓨터가 처리하게 하려면, 먼저 글을 숫자로 바꿔야 합니다.

이 변환을 하는 프로그램을 임베딩 모델이라고 하고, 결과로 나오는 숫자 묶음을 벡터라고 합니다.

벡터는 공간 안의 위치입니다

지도에서 어떤 장소를 가리킬 때 위도와 경도 두 숫자를 씁니다. 숫자 두 개로 평면 위 한 점이 정해집니다. 벡터도 같습니다. 다만 숫자가 두 개가 아니라 수백에서 수천 개입니다.

지도 위의 점  : (37.5, 127.0)          ← 숫자 2개
임베딩 벡터   : (0.12, -0.08, ..., 0.33)  ← 숫자 768개

숫자의 개수를 차원이라고 부릅니다. 768차원이라는 말은 숫자가 768개라는 뜻입니다.

중요한 점은 의미가 비슷한 글끼리 가까운 위치에 놓이도록 모델이 학습되어 있다는 것입니다. "수수료"에 관한 조각과 "이용료"에 관한 조각은 서로 가까운 곳에, "반출 규정"에 관한 조각은 멀리 떨어진 곳에 놓입니다.

얼마나 가까운지 재는 법

두 벡터가 얼마나 비슷한지는 둘 사이의 각도로 잽니다. 이것을 코사인 유사도라고 합니다.

값의미
1.0완전히 같은 방향 — 사실상 같은 의미
0.8 안팎상당히 관련 있음 — 검색에서 통과시키는 구간
0.0직각 — 서로 무관한 의미
-1.0정반대 방향

차원이 768이든 4096이든 계산 원리는 지도 위 두 점 사이의 각도를 재는 것과 완전히 같습니다. 우리 머릿속에 그림으로 그릴 수 없을 뿐, 수학적으로는 다르지 않습니다.

벡터가 정말 의미를 담고 있을까

이것을 확인해 주는 유명한 예가 있습니다.

벡터("왕") - 벡터("남자") + 벡터("여자")  ≈  벡터("여왕")

"남자에서 여자로 가는 방향"과 "왕에서 여왕으로 가는 방향"이 거의 같다는 뜻입니다. 성별이라는 개념이 이 공간의 특정 방향으로 자리를 잡고 있는 것입니다. 누가 정해 준 것이 아니라 학습 과정에서 저절로 그렇게 되었습니다.

5. 글자가 숫자가 되는 여섯 단계

"사과를 맛있게 먹었다"라는 문장이 벡터가 되는 과정을 순서대로 보겠습니다.

1단계 · 토크나이징 — 문장을 조각내기

먼저 문장을 모델이 아는 최소 단위로 쪼갭니다. 이 단위를 토큰이라고 합니다. 단어 전체일 수도 있고 단어의 일부일 수도 있습니다.

"안녕하세요 반갑습니다"
   → [안녕] [하세요] [반갑] [습니다]
   → [1234, 5678, 9012, 3456]      ← 각 토큰에 붙은 번호

번호를 붙일 때 참고하는 목록을 어휘 사전이라고 합니다. 모델을 만들 때 대량의 글에서 자주 나오는 조각을 골라 미리 만들어 두고, 이후에는 바뀌지 않습니다. BERT는 약 3만 개, 최신 모델은 10만 개 이상의 토큰을 가지고 있습니다.

2단계 · 초기 벡터 꺼내기

토큰 번호로 임베딩 테이블이라는 큰 표에서 해당 줄을 꺼냅니다. 이 표는 어휘 개수 × 차원 수 크기입니다. 어휘 3만 개에 768차원이면 2,300만 개의 숫자가 들어 있는 표가 됩니다.

이 표의 값들은 처음에는 아무 의미 없는 무작위 숫자입니다. 학습을 거치면서 의미가 비슷한 토큰끼리 비슷한 값을 갖도록 조정됩니다. 학습이 끝나면 고정되고, 이후에는 그냥 꺼내 쓰기만 합니다.

3단계 · 순서 정보 더하기

여기까지만 하면 "사과가 배를 먹었다"와 "배가 사과를 먹었다"가 똑같아집니다. 단어들의 순서 정보가 없기 때문입니다. 그래서 몇 번째 토큰인지를 나타내는 값을 더해 줍니다.

4단계 · 문맥 섞기

각 토큰이 문장 안의 다른 토큰들을 둘러보고, 관련 있는 쪽의 정보를 자기 값에 섞어 넣습니다. "먹었다"가 "사과를"을 참고하는 것이 이 단계입니다.

이 과정을 어텐션이라고 부릅니다. Transformer라는 구조의 핵심이며, 자세한 동작은 이 시리즈 5편에서 다룹니다. 지금은 단어들이 서로 정보를 주고받아 문맥을 반영한다는 정도로 충분합니다.

5단계 · 반복

4단계와 추가 가공을 여러 번 반복합니다. 작은 모델은 12번, 큰 모델은 수십 번 반복합니다. 반복할수록 표현이 정교해집니다.

6단계 · 하나로 합치기

지금까지는 토큰마다 벡터가 하나씩 있었습니다. 이것을 합쳐 문장 전체를 대표하는 벡터 하나를 만듭니다. 이 작업을 풀링이라고 합니다.

방식설명
평균 내기모든 토큰 벡터의 평균을 냅니다. 가장 널리 쓰입니다
맨 앞 토큰문장 시작을 알리는 특수 토큰의 값을 대표로 씁니다
맨 뒤 토큰마지막 토큰의 값을 씁니다. 최신 모델 일부가 이 방식입니다

최종 결과는 숫자 배열 하나입니다.

"사과를 맛있게 먹었다"
   → [0.1243, -0.0872, 0.3341, ..., 0.2201]   (숫자 768개)

이 벡터가 원문 텍스트, 출처, 쪽 번호와 함께 데이터베이스에 저장됩니다. 원문을 같이 저장하는 이유는, 검색으로 찾아낸 뒤 LLM에게 건네줄 것은 숫자가 아니라 원문이기 때문입니다.

6. 임베딩 모델과 차원 고르기

임베딩 모델은 종류가 많습니다. 언어, 차원, 운영 방식이 각각 다릅니다.

모델차원특징
Qwen3-Embedding-8B4,096한국어 성능이 높고 사내 서버 설치가 가능합니다
text-embedding-3-large3,072성능이 높지만 외부 API이며 과금됩니다
text-embedding-3-small1,536비용 절감형이며 성능도 준수합니다
multilingual-e5-large1,024여러 언어를 고르게 지원합니다
KoSimCSE-RoBERTa768한국어 문장 유사도에 특화되어 있습니다
all-MiniLM-L6384매우 가볍고 빠릅니다

사내 문서를 다룬다면 선택지가 크게 좁아집니다. 1편의 보안 안내에서 말씀드린 대로, 외부 API로 문서를 보낼 수 없으므로 사내 서버에 설치할 수 있는 모델이어야 합니다.

차원은 클수록 좋은가

구간장점단점
384~768 검색이 빠르고 저장 공간을 적게 씁니다 미묘한 의미 차이를 구분하기 어렵습니다
1,024~1,536 속도와 정확도의 균형이 좋습니다 양쪽 장점을 절반씩 가집니다
3,072 이상 복잡한 의미 관계까지 잡아냅니다 검색이 느리고 저장·계산 비용이 큽니다

차원의 저주 — 차원이 지나치게 커지면 모든 점 사이의 거리가 비슷해져서 오히려 가까운 것을 골라내기 어려워집니다. 무조건 높은 차원을 고르면 되는 것이 아니라 다루는 자료에 맞춰야 하며, 실무에서는 768~1,536 구간이 가장 많이 쓰입니다.

정리하면 다음과 같습니다.

문서 ─▶ 청킹 ─▶ 조각 여러 개
                    │
                    ├─▶ 임베딩 ─▶ 벡터 (숫자 묶음)
                    └─▶ 원문·출처·쪽번호
                              │
                              ▼
                        데이터베이스에 함께 저장

7. 자주 나오는 질문

문서 한 개가 벡터 한 개를 갖나요?

아닙니다. 청킹으로 나뉜 조각 하나가 벡터 하나를 갖습니다. 문서 100개를 각각 20조각으로 나눴다면 벡터는 2,000개가 됩니다. 규모가 큰 시스템에서는 수백만 개의 벡터를 다루게 됩니다. 다만 문서 분류나 추천처럼 문서 전체를 하나로 다루는 작업에서는 문서당 벡터 하나를 쓰기도 합니다.

수백 글자를 숫자 768개로 압축하면 내용이 손실되지 않나요?

손실됩니다. 벡터에서 원문을 복원할 수는 없습니다. 그런데 목적이 원문 복원이 아니라 비슷한 것 찾기이므로 문제가 되지 않습니다. 게다가 원문은 벡터와 함께 그대로 저장해 두기 때문에, 검색으로 찾아낸 뒤에는 온전한 원문을 LLM에게 건넬 수 있습니다.

조각 하나가 그 공간의 점 하나가 되는 것이 맞나요?

맞습니다. 조각 하나에서 나온 숫자들이 그대로 좌표가 됩니다. 의미가 비슷한 조각들은 그 공간에서 서로 가까운 곳에 모여 있게 됩니다.

오버랩을 크게 잡으면 더 좋은가요?

어느 정도까지는 그렇지만 무한정은 아닙니다. 겹치는 만큼 조각 수가 늘어나 저장 공간과 검색 시간이 함께 늘어납니다. 또 같은 내용이 여러 조각에 들어 있으면 검색 결과 상위가 중복된 조각으로 채워질 수 있습니다. 10~20%에서 시작하시기를 권합니다.

청킹과 임베딩을 직접 프로그래밍해야 하나요?

아닙니다. 이미 만들어진 도구가 있습니다. 4편에서 다룰 LangChain에 재귀적 청킹이 기본으로 들어 있고, 설정 두 줄이면 동작합니다. 임베딩도 모델을 불러와 함수를 한 번 호출하면 됩니다.

한국어 문서인데 영어 모델을 쓰면 안 되나요?

동작은 하지만 성능이 떨어집니다. 영어 위주로 학습된 모델은 한국어 토큰을 지나치게 잘게 쪼개고, 한국어 표현들 사이의 미묘한 차이도 잘 잡지 못합니다. 한국어 자료를 다룬다면 한국어를 충분히 학습한 모델을 고르시기 바랍니다.

이제 문서가 벡터로 바뀌어 데이터베이스에 들어갔습니다. 다음 편에서는 수백만 개의 벡터 중에서 정확한 것을 골라내는 방법을 다룹니다.

조회 47댓글 0 내려받기 통계
0

김영민 님의 다른 글

김영민 님 글 모아보기 →

댓글 0

첫 댓글을 남겨보세요.
댓글을 쓰려면 로그인이 필요합니다.