들어가며
지난 글에서는 FAQ를 검색해 LLM에 전달하는 RAG의 답변 생성 과정을 살펴봤습니다. 이번에는 그 안에서 반복해서 등장하는 Token, Embedding, Context Window를 정리합니다. Java 백엔드 개발자의 관점에서 문자열 처리와 자료구조에 연결해 설명하되, 비유와 실제 모델 동작은 구분합니다.
이 글은 개념 학습을 위한 글입니다. 모델 설치, 추론 실행, 성능 측정은 수행하지 않았습니다. 코드와 수치는 설명용이며 실제 LLM의 토큰 ID나 임베딩 측정 결과가 아닙니다.
목차
- Token: 모델이 처리하는 텍스트 단위
- Embedding: 숫자 벡터로 표현하는 방법
- Context Window: 한 번에 참고할 수 있는 범위
- 세 개념이 RAG에서 만나는 과정
- 설명용 코드와 예상 결과
- 흔한 오해와 해결 방향
- 결론과 복습
1. Token: 모델이 처리하는 텍스트 단위
LLM에 문자열을 전달하면 먼저 토크나이저가 텍스트를 토큰으로 변환합니다. 토큰은 단어 전체, 단어 일부, 기호 등일 수 있습니다. 공백으로 나눈 단어 수나 Java의 String.length()와 토큰 수는 다릅니다.
예를 들어 "반품 가능한가요?"의 정확한 분할과 토큰 수는 선택한 모델의 토크나이저로 확인해야 합니다. 여기서 임의로 몇 개라고 단정할 수 없습니다. 같은 문장도 토크나이저에 따라 다르게 나뉠 수 있습니다.
각 토큰에는 어휘 목록에 대응하는 정수 ID가 있습니다. 이 ID는 식별자이며, ID가 큰 토큰이 더 중요하거나 두 ID의 차이가 작으면 의미가 비슷하다는 뜻은 아닙니다. DB의 PK 값이 서로 가깝다고 두 행의 내용이 유사하지 않은 것과 비슷합니다.
Java의 split(" ")은 공백 기준 분리입니다. 실제 토크나이저는 학습된 어휘와 BPE, WordPiece, Unigram 등의 규칙을 사용하므로 단순 split으로 대체할 수 없습니다. 채팅 모델은 역할과 메시지 경계를 나타내는 특수 토큰도 사용할 수 있습니다.
2. Embedding: 숫자 벡터로 표현하는 방법
토큰 ID만으로는 신경망이 언어의 관계를 처리하기 어렵습니다. 일반적인 LLM은 토큰 ID에 대응하는 학습된 벡터를 임베딩 테이블에서 가져와 계산에 사용합니다. Java로 비유하면 Map<Integer, float[]>에서 ID에 해당하는 배열을 찾는 모습과 비슷합니다. 실제 구현은 큰 행렬을 이용한 연산입니다.
벡터는 여러 숫자로 이루어진 표현입니다. 설명용으로 [0.12, -0.34, 0.56]처럼 적을 수 있지만, 각 숫자가 각각 반품, 배송, 고객 같은 사람이 정한 항목을 의미하지는 않습니다. 실제 차원과 값은 모델마다 다릅니다.
여기서 두 가지 임베딩을 구분해야 합니다.
| 구분 | LLM의 입력 토큰 임베딩 | RAG 검색용 문장·문서 임베딩 |
|---|---|---|
| 표현 대상 | 개별 토큰 | 질문이나 문서 조각 등 |
| 목적 | 모델 내부의 언어 계산 | 관련 자료 검색 |
| 일반적인 사용 | 토큰 ID에 대응하는 벡터를 모델에 입력 | 질문과 문서의 벡터 유사도 비교 |
입력 토큰 임베딩 이후에는 Transformer 계층이 주변 문맥을 반영해 표현을 갱신합니다. 같은 토큰이라도 등장한 문맥에 따라 이후의 내부 표현은 달라질 수 있습니다.
RAG에서는 검색에 적합하게 학습된 임베딩 모델을 별도로 사용하는 경우가 많습니다. 질문과 문서를 호환되는 같은 벡터 공간으로 변환한 뒤 코사인 유사도나 내적 등으로 비교합니다. 벡터의 차원 수만 같다고 서로 다른 모델의 벡터를 비교해도 되는 것은 아닙니다.
유사도는 정답 확률이 아닙니다. 내용이 비슷하게 표현되었더라도 정책의 적용 날짜나 고객 조건이 다르면 답변 근거로 부적합할 수 있습니다. 검색 이후에도 조건과 출처를 확인해야 합니다.
3. Context Window: 한 번에 참고할 수 있는 범위
Context Window는 모델이 한 번의 처리 과정에서 다룰 수 있는 토큰 범위입니다. 생성형 LLM의 일반적인 설계에서는 입력 문맥과 생성되는 답변이 함께 이 범위에 들어갑니다. 서비스의 별도 출력 한도와 실제 런타임 설정도 확인해야 합니다.
입력은 사용자 질문만이 아닙니다. 시스템 지시문, 대화 기록, RAG로 가져온 자료, 도구 결과, 채팅 형식을 위한 토큰 등이 포함될 수 있습니다.
가상의 8,192토큰 한도를 예로 들겠습니다. 다음 숫자는 특정 모델의 측정값이 아닙니다.
| 항목 | 설명용 토큰 수 |
|---|---|
| 지시문·질문·기록·형식 토큰 | 2,000 |
| 검색 문서 | 4,000 |
| 답변에 남겨둘 예산 | 1,500 |
| 합계 | 7,500 |
| 여유 | 692 |
입력만으로 한도를 채우면 긴 답변을 생성할 여유가 없어집니다. 한도를 넘겼을 때 오류를 내는지, 일부 내용을 잘라내는지, 대화 기록을 줄이는지는 서버와 애플리케이션 설정에 따라 달라집니다.
Context Window는 영구 기억이 아닙니다. 백엔드가 DB에 대화 기록을 저장하더라도 다음 요청에 포함하지 않은 기록을 모델이 자동으로 읽지는 않습니다. RAG 문서를 문맥에 넣는 것 역시 모델의 가중치를 재학습시키는 작업과 다릅니다.
문맥이 길면 자료를 많이 전달할 수 있지만, 모든 정보를 정확하게 활용한다는 보장은 없습니다. 처리 비용과 지연, KV 캐시 메모리 부담도 커질 수 있습니다. Local LLM에서는 VRAM과 실제 설정을 함께 고려해야 합니다.
4. 세 개념이 RAG에서 만나는 과정
"반품 배송비는 누가 내나요?"라는 질문을 예로 보겠습니다.
- 문서를 적절한 조각으로 나누고 검색용 임베딩을 만들어 저장합니다.
- 질문을 검색용 임베딩 모델로 변환합니다.
- 질문과 관련된 문서 조각을 검색합니다.
- 검색된 문서의 원문, 질문, 지시문을 생성 모델의 입력에 넣습니다.
- 생성 모델의 토크나이저가 그 입력을 토큰 ID로 변환합니다.
- 생성 모델이 토큰 임베딩과 Transformer 계산을 거쳐 다음 토큰을 순차적으로 생성합니다.
일반적인 텍스트 RAG에서는 검색 벡터 자체를 답변 생성 LLM에 붙여 넣는 것이 아니라, 검색으로 찾은 원문을 전달합니다. 검색용 임베딩 모델과 답변 생성 모델의 토크나이저가 서로 다를 수도 있습니다.
문서를 전부 넣지 않는 이유는 단지 길이 제한 때문만은 아닙니다. 관련 없는 내용을 줄이고 필요한 근거를 집중적으로 제공하는 목적도 있습니다. 다만 검색에서 근거를 놓치면 답변도 잘못될 수 있으므로 검색 품질을 별도로 검증해야 합니다.
5. 설명용 코드와 예상 결과
아래 코드는 토큰 ID에서 벡터를 조회한다는 개념과 문맥 예산 계산을 보여주는 Python 예제입니다. 실제 토크나이저나 LLM을 구현한 코드가 아니며 외부 패키지는 필요하지 않습니다.
# 설명용으로 직접 만든 ID와 벡터입니다.
token_ids = [10, 20]
embedding_table = {
10: [0.1, 0.2, 0.3],
20: [0.4, 0.5, 0.6],
}
vectors = [embedding_table[token_id] for token_id in token_ids]
print("조회한 벡터:", vectors)
context_limit = 8192
other_input_tokens = 2000
retrieved_tokens = 4000
reserved_output_tokens = 1500
remaining = context_limit - (
other_input_tokens + retrieved_tokens + reserved_output_tokens
)
print("남은 토큰 예산:", remaining)
Python이 설치된 Windows 환경에서는 token_demo.py로 저장하고 터미널에서 다음 명령으로 실행할 수 있습니다.
py token_demo.py
예상 출력은 다음과 같습니다. 이 글에서는 실행하지 않았으므로 실측 결과로 제시하지 않습니다.
조회한 벡터: [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]
남은 토큰 예산: 692
실제 서비스에서는 토큰 수를 직접 지정하는 대신 생성 모델에 맞는 토크나이저로 완성된 입력을 계산해야 합니다. 위 코드는 원리를 보여주기 위한 예산 계산 예제입니다.
6. 흔한 오해와 해결 방향
실제 발생한 장애 기록이 아니라, 실습 전에 알아둘 점을 정리했습니다.
| 오해·문제 | 원인 또는 구분 | 확인·해결 방향 |
|---|---|---|
| 글자 수로 입력 한도를 계산한다 | 글자와 토큰은 다른 단위다 | 모델의 토크나이저로 입력을 계산한다 |
| 임베딩 유사도가 높으면 정답이다 | 유사도는 사실의 진위를 검증하지 않는다 | 출처·날짜·적용 조건을 확인한다 |
| 질문만 짧으면 문맥 한도에 걸리지 않는다 | 지시문·기록·문서도 입력에 포함된다 | 최종 요청 전체와 출력 예산을 확인한다 |
| 문서를 입력하면 모델이 영구적으로 학습한다 | 문맥 제공과 가중치 학습은 다르다 | 다음 요청에도 필요한 근거를 검색해 제공한다 |
| 벡터 차원만 같으면 모델을 바꿔도 된다 | 모델마다 벡터 공간이 다를 수 있다 | 임베딩 모델을 바꾸면 문서 재임베딩을 검토한다 |
7. 결론과 복습
Token은 텍스트를 처리하는 단위이고, Embedding은 대상을 숫자 벡터로 표현하는 방법이며, Context Window는 한 번에 처리할 수 있는 문맥의 범위입니다.
Java 개발자의 관점에서는 Token ID를 식별자, 입력 임베딩을 ID에 대응하는 숫자 배열, Context Window를 요청에 사용할 수 있는 토큰 예산으로 생각하면 출발점을 잡기 쉽습니다. 다만 이것은 비유이며, 모델의 언어 처리는 학습된 신경망 연산을 통해 이루어집니다.
이번 글을 읽고 다음 세 질문을 설명할 수 있으면 핵심을 이해한 것입니다.
- String.length()로 LLM의 입력 토큰 수를 계산할 수 없는 이유는 무엇인가?
- RAG의 검색용 임베딩과 생성 모델의 토큰 임베딩은 어떻게 다른가?
- 검색 문서를 많이 넣을 때 답변을 위한 토큰 예산을 남겨야 하는 이유는 무엇인가?
답변의 핵심은 각각 토크나이저에 따른 분할, 표현 대상과 목적의 차이, 입력과 출력이 사용하는 문맥 범위입니다. 다음 실습에서는 모델을 선택하고 실제 토크나이저로 한국어 문장의 토큰 수를 확인하는 과정을 별도로 기록할 수 있습니다.
참고 자료
- Hugging Face Transformers — Tokenizer summary
- Hugging Face BERT 문서
- Sentence Transformers — Semantic Search
이전 글
'AI·LLM' 카테고리의 다른 글
| [RAG 기초] 코사인 유사도 — 질문과 문서의 관련성을 숫자로 비교하는 방법 (0) | 2026.10.17 |
|---|---|
| [LLM·RAG 기초] FAQ 예제로 따라가는 RAG의 답변 생성 과정 (0) | 2026.10.14 |