토큰과 컨텍스트 창으로 LLM의 기억 이해하기
글자 수와 토큰의 차이, 대화가 길어질 때 앞 내용을 잊어버리는 컨텍스트 창의 구조적 원리와 슬라이딩 윈도우 관리법을 알아봅니다.
무료 공개 · 최근 수정
30분 전 나눈 대화를 모델이 갑자기 잊어버리는 까닭
고객 지원 AI 봇을 런칭한 지 일주일 만에 당황스러운 장애 보고가 접수되었습니다. 고객과 상담을 시작한 지 30분쯤 지나자 봇이 직전에 확인했던 주문 번호를 다시 묻거나, 대화 초반에 지정한 금지 품목 환불 규정을 무시하고 승인해 버리는 오류를 일으킨 것입니다. 게다가 월말 API 청구서를 열어보니 대화가 길어질수록 토큰 사용량이 눈덩이처럼 불어나 예상 비용의 5배를 초과했습니다.
많은 사용자와 초급 개발자는 AI가 인간처럼 두뇌 속에 기억을 차곡차곡 누적한다고 생각하지만, 신경망 모델은 이전 대화를 스스로 기억하는 영구 상태(State)를 내부에 저장하지 않습니다. 사용자가 새 메시지를 전송할 때마다 클라이언트나 백엔드 서버가 과거의 대화 기록 전체를 다시 묶어 모델에 매번 통째로 전달하는 구조이기 때문입니다.
이때 모델이 한 번의 추론에서 한눈에 담을 수 있는 텍스트의 물리적 허용 용량이 정해져 있으며, 그 단위를 구성하는 기본 블록이 토큰(Token)입니다. 아무리 지능이 높은 모델이라도 허용된 용량을 초과하는 순간 과거의 대화 기록은 시스템 메모리 밖으로 잘려 나가며, 모델 입장에서는 그 내용이 애초에 존재하지 않았던 것과 같습니다. 따라서 대화형 인공지능과 효율적으로 상호작용하고 안정적인 에이전트 시스템을 구축하려면 토큰의 분할 메커니즘과 컨텍스트 창의 구조적 한계를 정확히 파악해야 합니다.
글자 수와 다른 토큰: BPE 분할 원리
컴퓨터가 자연어를 숫자로 처리하기 위해 텍스트를 의미 단위의 작은 조각으로 쪼갠 뒤 고유한 정수 ID를 부여한 결과물이 토큰입니다. 현대 거대 언어 모델은 텍스트를 단순한 공백 단위나 글자(Character) 단위로 자르지 않고, 자주 함께 등장하는 바이트 연속열을 압축 병합하는 바이트 페어 인코딩(Byte-Pair Encoding; BPE) 알고리즘을 사용합니다.
BPE 알고리즘은 모든 개별 문자(알파벳, 기호, UTF-8 바이트)를 기본 어휘 사전으로 삼아 출발합니다. 이후 말뭉치 전체에서 가장 빈번하게 연속 출현하는 두 바이트 쌍을 찾아 새 단어로 사전에 추가하는 과정을 수만 번 반복합니다. 예를 들어 'low', 'lower', 'lowest'라는 단어가 반복된다면 'l', 'o', 'w'가 합쳐져 'low'라는 하나의 독립 토큰이 되고, 접미사 'er'과 'est'가 별도의 토큰으로 등재됩니다. 이 방식을 채택하면 사전에 없는 희귀 단어나 오타가 들어와도 문자가 완전히 깨지지 않고 최소 단위 바이트들로 안전하게 쪼개져 처리될 수 있습니다.
영어 단어는 대략 1개 단어가 1.3개 내외의 토큰으로 압축되지만, 한국어나 일본어 같은 다국어 텍스트는 토큰 소모량이 2배에서 3배 이상 커지는 현상이 흔히 발생합니다. OpenAI의 GPT-4 계열 초기 어휘 사전(cl100k_base) 기준 영어는 'artificial intelligence'라는 2단어가 단 2개의 토큰으로 변환되지만, 한국어 '인공지능'은 4~6개의 토큰으로 잘게 쪼개졌습니다. 이는 한국어 음절이 UTF-8 인코딩에서 1글자당 3바이트를 차지하는 데다, 서구권 중심의 사전 구축 말뭉치에서 한국어 형태소 조합 빈도가 상대적으로 적게 반영되었기 때문입니다. 동일한 분량의 정보를 전달하더라도 비영어권 언어가 API 비용을 더 많이 지불하고 컨텍스트 한계에 훨씬 일찍 도달하게 되는 구조적 요인입니다.
그림: 텍스트 토큰화와 컨텍스트 적재 및 슬라이딩 윈도우 제거 흐름
위 그림은 사용자가 입력한 자연어가 토크나이저를 통해 정수 배열로 변환된 뒤, 고정 크기의 윈도우 버퍼를 가득 채우고 한계를 넘어서면 과거 데이터가 밀려나는 전체 수명 주기를 나타냅니다.
컨텍스트 창의 물리적 상한과 입출력 비대칭성
모델이 입력과 출력을 합쳐 한 번에 연산할 수 있는 전체 토큰 버퍼의 크기를 컨텍스트 창(Context Window)이라고 부릅니다. 트랜스포머의 셀프 어텐션은 모든 입력 토큰 쌍 간의 상관관계를 행렬 곱셈으로 연산하므로, 컨텍스트 창의 크기는 곧 GPU 메모리와 직접 연결된 하드웨어의 물리적 한계선입니다.
현대 모델들은 컨텍스트 창을 대폭 확장하고 있지만, 입력 허용량과 한 번에 생성 가능한 최대 출력량 사이에는 뚜렷한 비대칭성이 존재합니다.
| 모델 명칭 | 전체 컨텍스트 창 (입력 상한) | 최대 출력 토큰 (생성 상한) |
|---|---|---|
| OpenAI GPT-4o | 128,000 토큰 (약 128k) | 16,384 토큰 (약 16k) |
| Anthropic Claude 3.5 Sonnet | 200,000 토큰 (약 200k) | 8,192 토큰 (약 8k) |
| Google Gemini 1.5 Pro | 2,000,000 토큰 (약 2M) | 8,192 토큰 (약 8k) |
128,000 토큰의 입력 창을 가진 모델이라 하더라도 사용자가 "책 한 권을 통째로 써 줘"라고 요청했을 때 한 번에 128,000 토큰 분량의 글을 토해낼 수는 없습니다. 출력은 8k~16k 수준에서 엄격히 차단되며, 이를 넘어서는 방대한 결과물을 얻으려면 챕터별로 나누어 단계적으로 요청해야 합니다.
기억을 유지하기 위한 슬라이딩 윈도우와 중간 유실
장기 대화 애플리케이션에서는 누적된 토큰 수가 허용된 컨텍스트 크기를 초과할 때 가장 오래된 대화 턴을 먼저 제거하는 슬라이딩 윈도우(Sliding Window) 버퍼링을 사용합니다. 전체 창 크기가 8,000토큰으로 제한된 챗봇이 있다면, 10번째 대화에 이르러 이전 1~3번째 대화 기록을 프롬프트 목록에서 선입선출(FIFO) 방식으로 삭제하는 구조입니다. 이 때문에 사용자는 "AI가 대화 초반의 설정을 갑자기 잊었다"고 느끼게 됩니다.
설령 컨텍스트 창이 200k 토큰으로 넉넉하여 대화 내용이 잘리지 않고 온전히 들어간다 하더라도 정보가 완벽하게 검색되는 것은 아닙니다. 2023년 스탠퍼드 대학교의 넬슨 리우(Nelson F. Liu) 연구팀은 논문을 통해 프롬프트의 맨 앞이나 맨 뒤에 위치한 정보는 높은 확률로 인출되지만, 본문 중간에 묻힌 정보는 모델이 인출하지 못하고 놓치는 중간 유실 현상(Lost in the Middle)을 입증했습니다. 어텐션 가중치가 문장의 시작 부분(시스템 프롬프트 영역)과 끝부분(질문 및 최근 대화 영역)에 집중적으로 쏠리는 모델 구조상의 특성 때문입니다.
[참고] 한국어 토큰 효율을 개선한 신규 토크나이저 어휘 사전
최근 발표되는 Llama 3나 Gemma 2 등의 최신 오픈소스 모델은 어휘 사전 크기를 기존 32,000개 수준에서 128,000개 및 256,000개로 대폭 확장했습니다. 어휘 사전에 빈번한 한글 복합 형태소와 음절 조합이 대거 기본 토큰으로 편입되면서, 동일한 한국어 문장을 표현할 때 필요한 토큰 수가 과거 대비 30~50% 이상 절감되었습니다.
프롬프트 엔지니어링과 메모리 관리 전략
컨텍스트 제약과 정보 유실을 방어하기 위해 실무 시스템에서는 세 가지 핵심 원칙을 적용합니다.
첫째, 핵심 질문과 주요 제약 조건은 항상 프롬프트의 맨 마지막에 배치합니다. 앤트로픽(Anthropic) 공식 문서에서도 긴 문서를 참조용으로 제공할 경우 문서를 상단에 두고, 구체적인 지시사항과 질문을 프롬프트 맨 끝에 작성할 것을 명시적으로 권장합니다. 이렇게 배치하면 최근 입력된 토큰에 강한 어텐션이 걸려 중간 유실 문제를 대폭 줄일 수 있습니다.
둘째, 슬라이딩 윈도우로 과거 데이터를 단순 삭제하는 대신 주기적 요약 파이프라인(Summarization Buffer)을 도입합니다. 누적 대화가 일정 토큰 이상 쌓이면 백그라운드에서 별도의 작은 모델을 호출하여 "지금까지의 핵심 결정 사항과 고유 명사 요약"을 생성한 뒤, 오래된 개별 턴을 삭제하고 시스템 프롬프트에 요약본만 갱신해 주입하는 방식입니다.
셋째, 독립된 주제로 대화가 전환될 때는 과감히 세션을 초기화(Reset)합니다. 이전 주제의 불필요한 토큰들이 남아 있으면 비용이 매 턴마다 누적 청구될 뿐 아니라, 모델의 주의력이 분산되어 새로운 작업의 품질을 떨어뜨립니다.
넷째, 고정된 대용량 컨텍스트에는 프롬프트 캐싱(Prompt Caching)을 활용합니다. Anthropic Claude와 OpenAI GPT-4o 등 최신 API는 앞부분에 반복해서 입력되는 시스템 지침이나 참조 문서의 프리필(KV 캐시) 상태를 서버에 보존하는 기능을 제공합니다. 이를 활용하면 캐시 적중 시 입력 토큰 비용이 50~90% 절감되고 응답 지연 시간도 크게 단축됩니다.
파이썬 코드로 확인하는 BPE 빈도 병합 메커니즘
다음 예제는 연속된 문자 쌍의 빈도를 집계하여 가장 잦은 쌍을 단일 토큰으로 병합하는 BPE 알고리즘의 1단계 루프를 순수 파이썬 코드로 보여 줍니다.
import collections
# 초기 코퍼스: 단어 끝 구분자 '</w>'를 포함한 개별 문자 분할
corpus = [
["l", "o", "w", "</w>"],
["l", "o", "w", "e", "r", "</w>"],
["n", "e", "w", "e", "s", "t", "</w>"],
["w", "i", "d", "e", "s", "t", "</w>"],
]
# 1. 연속된 인접 문자 쌍(Pair) 빈도 계산
pair_counts = collections.defaultdict(int)
for word in corpus:
for i in range(len(word) - 1):
pair_counts[(word[i], word[i + 1])] += 1
# 2. 가장 높은 빈도를 기록한 쌍 추출
best_pair = max(pair_counts, key=pair_counts.get)
print("가장 빈번한 바이트 쌍:", best_pair, "등장 횟수:", pair_counts[best_pair])
# 3. 해당 쌍을 단일 토큰으로 병합
merged_corpus = []
for word in corpus:
new_word = []
i = 0
while i < len(word):
if i < len(word) - 1 and (word[i], word[i + 1]) == best_pair:
new_word.append("".join(best_pair))
i += 2
else:
new_word.append(word[i])
i += 1
merged_corpus.append(new_word)
print("병합 후 첫 번째 단어 토큰 분할:", merged_corpus[0])실행 결과는 다음과 같습니다.
가장 빈번한 바이트 쌍: ('l', 'o') 등장 횟수: 2
병합 후 첫 번째 단어 토큰 분할: ['lo', 'w', '</w>']예시 출력처럼 분리되어 있던 'l'과 'o'가 가장 높은 동시 출현 빈도를 바탕으로 'lo'라는 하나의 병합 토큰으로 결합됩니다. 이 반복을 수만 번 거치면서 영어는 완성형 단어가 하나의 토큰이 되고, 한국어는 자주 쓰이는 음절과 조사가 독립된 토큰으로 사전화됩니다. 토큰의 생성 원리를 이해하면 프롬프트 작성 시 공백이나 특수 기호가 어떻게 비용과 성능에 영향을 미치는지 명확하게 파악할 수 있습니다.
참고 문서
설명이 어렵거나 잘못된 부분을 발견하셨나요?
문서 수정 의견 보내기