Skip to main content
Summary

문장을 자르는 방식이 모델의 세계를 바꾼다

지난 장에서 우리는 텍스트가 그대로 모델에 들어가지 않는다는 사실을 보았습니다. 모델은 "안녕하세요"라는 글자를 눈으로 읽지 않습니다. 먼저 텍스트를 작은 조각으로 나누고, 각 조각을 숫자 ID로 바꾼 뒤, 그 숫자를 바탕으로 계산을 시작합니다.

이때 텍스트를 작은 조각으로 나누는 과정을 토큰화라고 합니다. 그리고 모델이 알고 있는 토큰 목록을 어휘 사전, 영어로는 vocabulary라고 부릅니다.

처음에는 이렇게 생각하기 쉽습니다.

"그냥 단어별로 나누면 되는 것 아닌가?"

예를 들어 다음 문장을 보겠습니다.

나는 오늘 커피를 마셨다.

사람에게는 "나는", "오늘", "커피를", "마셨다"처럼 단어 단위로 나누는 것이 자연스럽습니다. 하지만 LLM 입장에서는 그렇게 단순하지 않습니다. 세상에는 너무 많은 단어가 있고, 새로운 단어가 매일 생기며, 같은 뜻의 말도 조사와 어미가 붙으면서 수없이 변합니다. "커피", "커피를", "커피가", "커피는", "커피만", "커피라도"를 모두 완전히 다른 단어로 등록한다면 어휘 사전은 금방 커져 버립니다.

그래서 현대 LLM은 대개 단어보다 작고, 글자보다는 큰 조각을 사용합니다. 이런 조각을 하위 단어 토큰, 또는 서브워드 토큰이라고 합니다. 이 장의 핵심은 바로 여기에 있습니다.

LLM은 문장을 단어장처럼 읽는 것이 아니라, 자주 등장하는 텍스트 조각들의 조합으로 읽습니다. 이 조각을 어떻게 정하느냐에 따라 모델의 비용, 속도, 다국어 성능, 심지어 가끔 보이는 이상한 답변까지 달라질 수 있습니다.

토큰화와 어휘 사전

단어 단위 토큰화의 한계

가장 쉬운 방법은 공백을 기준으로 문장을 나누는 것입니다.

I like coffee.
→ [I] [like] [coffee.]

영어처럼 단어 사이에 공백이 비교적 분명한 언어에서는 그럴듯해 보입니다. 하지만 곧 문제가 생깁니다. "coffee"와 "coffee."는 다릅니다. "Coffee"와 "coffee"도 다릅니다. "coffees", "coffee-like", "coffeehouse"까지 등장하면 어디까지 같은 단어로 보고 어디부터 다른 단어로 볼지 애매해집니다.

한국어에서는 문제가 더 두드러집니다.

커피를 마셨다
커피가 맛있다
커피는 필요하다
커피라도 마시자

사람은 이 네 문장에서 "커피"라는 핵심 의미를 쉽게 알아봅니다. 하지만 공백 기준으로만 나누면 "커피를", "커피가", "커피는", "커피라도"가 모두 다른 조각이 됩니다. 어휘 사전에 이 모든 변형을 넣어야 할까요? 그러면 자주 쓰이지 않는 조합이 끝없이 늘어납니다.

또 하나의 문제는 처음 보는 단어입니다. 예를 들어 어떤 사람이 새 서비스 이름을 "토큰고양이랩스"라고 지었다고 해봅시다. 단어 단위 어휘 사전에 이 이름이 없다면 모델은 곤란해집니다. 모르는 단어를 통째로 하나의 "알 수 없음" 표시로 바꾸면, 그 안에 들어 있는 "토큰", "고양이", "랩스" 같은 힌트도 잃어버립니다.

반대로 글자 단위로 나누면 어떨까요?

토큰고양이랩스
→ [토] [큰] [고] [양] [이] [랩] [스]

이 방식은 처음 보는 단어도 처리할 수 있다는 장점이 있습니다. 하지만 문장이 너무 길어집니다. "안녕하세요"도 다섯 글자, 긴 문서는 수만 글자가 됩니다. 모델은 한 번에 처리할 수 있는 토큰 수에 제한이 있으므로, 글자 단위 토큰화는 많은 경우 비효율적입니다.

그래서 등장한 절충안이 하위 단어 토큰화입니다. 자주 나오는 표현은 크게 묶고, 드문 표현은 작은 조각으로 나눕니다.

하위 단어 토큰화의 기본 아이디어

하위 단어 토큰화는 아주 실용적인 질문에서 출발합니다.

"자주 나오는 조각은 하나로 저장하고, 드문 단어는 조각을 조립해서 표현하면 어떨까?"

예를 들어 어휘 사전에 다음과 같은 조각들이 있다고 해봅시다.

[커피] [를] [가] [는] [라도] [마시] [었다] [자]

그러면 "커피를", "커피가", "커피라도"를 모두 통째로 외우지 않아도 됩니다.

커피를   → [커피] [를]
커피가   → [커피] [가]
커피라도 → [커피] [라도]

이 방식의 장점은 분명합니다. 자주 등장하는 핵심 조각을 재사용할 수 있고, 처음 보는 단어도 어느 정도 작은 조각으로 나누어 처리할 수 있습니다.

현대 LLM에서 널리 쓰이는 하위 단어 토큰화 방식에는 여러 종류가 있습니다. 대표적으로 다음 세 가지를 자주 만납니다.

  • BPE(Byte Pair Encoding): 자주 함께 등장하는 인접 조각을 반복해서 합치며 어휘를 만듭니다.

  • WordPiece: 단어를 조각으로 나누되, 학습 데이터에서 그 조각들이 단어를 잘 설명하도록 선택합니다.

  • Unigram: 가능한 조각 후보를 많이 만든 뒤, 그중 문장을 잘 표현하는 조합을 확률적으로 고릅니다.

이름은 조금 낯설지만 공통된 목표는 같습니다. 문장을 너무 잘게 쪼개지도 않고, 너무 큰 단어 단위로만 보지도 않는 것입니다. 모델이 자주 보는 표현은 효율적으로 다루고, 낯선 표현은 작은 부품으로 분해해 처리합니다.

비유하자면 하위 단어 토큰화는 레고 상자와 비슷합니다. "자동차" 완성품만 잔뜩 넣어두면 자동차는 빨리 만들 수 있지만, 새로운 모양을 만들기 어렵습니다. 반대로 가장 작은 블록만 있으면 무엇이든 만들 수 있지만 시간이 오래 걸립니다. 좋은 토큰화는 자주 쓰는 큰 블록과 어디에나 쓸 수 있는 작은 블록을 적절히 섞어두는 일입니다.

BPE는 자주 붙는 조각을 합친다

BPE는 하위 단어 토큰화를 이해할 때 가장 직관적인 방식입니다. 원래는 데이터 압축에서 쓰이던 아이디어지만, 언어 모델에서도 널리 사용됩니다.

아주 단순화해서 설명하면 BPE는 이렇게 움직입니다.

처음에는 텍스트를 아주 작은 단위로 봅니다. 글자일 수도 있고, 바이트일 수도 있습니다. 그다음 학습 데이터에서 자주 붙어 나오는 두 조각을 찾습니다. 예를 들어 "언"과 "어"가 자주 붙어 "언어"로 등장한다면, 둘을 합쳐 새로운 조각 "언어"를 만듭니다. 이런 합치기를 반복하면 자주 등장하는 표현은 점점 더 큰 토큰이 됩니다.

가상의 예를 보겠습니다.

처음: [언] [어] [모] [델]
자주 붙는 쌍 합치기: [언어] [모] [델]
또 합치기: [언어] [모델]

만약 "언어"와 "모델"이 학습 데이터에서 매우 자주 등장한다면, 어휘 사전에 각각 등록될 가능성이 커집니다. 더 자주 등장하는 " 언어모델" 같은 표현이 있다면 공백을 포함한 더 큰 조각으로 등록될 수도 있습니다.

여기서 중요한 점은 토큰이 사람이 생각하는 단어와 꼭 일치하지 않는다는 것입니다. 모델의 토큰은 문법책의 단어가 아니라, 학습 데이터 안에서 자주 반복된 문자열 조각입니다.

그래서 어떤 단어는 하나의 토큰이 되고, 어떤 단어는 여러 토큰으로 나뉩니다. "ChatGPT"처럼 자주 등장하는 표현은 통째에 가깝게 처리될 수 있지만, 드문 신조어나 긴 전문 용어는 여러 조각으로 나뉠 수 있습니다. 정확한 결과는 사용하는 모델과 토크나이저에 따라 달라집니다.

WordPiece와 Unigram은 무엇이 다를까

BPE가 "자주 붙는 쌍을 합친다"는 느낌이라면, WordPiece와 Unigram은 조금 다른 관점에서 조각을 고릅니다. 하지만 처음 LLM을 공부하는 단계에서는 세부 수식보다 큰 감각이 더 중요합니다.

WordPiece는 이름처럼 단어를 여러 조각으로 나누어 표현합니다. 어떤 조각을 선택했을 때 학습 데이터의 단어들을 더 잘 설명할 수 있는지를 따져 어휘를 구성합니다. 일부 토크나이저에서는 단어의 중간이나 뒤에 붙는 조각임을 표시하기 위해 특별한 기호를 쓰기도 합니다. 예를 들어 영어권 예시에서 play, ##ing처럼 표시하는 방식이 있습니다. 여기서 ##ing은 독립 단어 "ing"라기보다 앞 단어에 이어 붙는 조각이라는 뜻에 가깝습니다.

Unigram 방식은 후보 조각을 많이 준비한 뒤, 문장을 설명하는 데 덜 중요한 조각을 줄여가며 어휘를 다듬는 방식으로 이해할 수 있습니다. 하나의 단어도 여러 방식으로 나눌 수 있는데, 그중 확률적으로 가장 그럴듯한 분할을 선택합니다.

세 방식의 내부 절차는 다르지만, 독자가 지금 꼭 붙잡아야 할 핵심은 하나입니다.

토크나이저는 사람이 보기 좋은 단어 목록을 만드는 도구가 아닙니다. 모델이 계산하기 좋은 텍스트 조각 목록을 만드는 도구입니다.

이 관점을 잡으면 이후에 등장할 비용 계산, 컨텍스트 윈도우, RAG 청킹, 프롬프트 설계가 훨씬 선명해집니다.

어휘 사전은 단어장이 아니라 부품 상자다

어휘 사전이라고 하면 학교에서 외우던 영어 단어장이 떠오를 수 있습니다. 하지만 LLM의 어휘 사전은 그런 단어장이 아닙니다. 더 정확히 말하면 토큰과 토큰 ID의 대응표입니다.

가상의 어휘 사전을 보겠습니다.

토큰        ID
----------------
"나는"      1024
" 커피"     8153
"를"        317
" 마셨"     22019
"다"        91
"."         13

모델에 "나는 커피를 마셨다."라는 문장이 들어오면 토크나이저는 문장을 토큰으로 나누고, 각 토큰을 ID로 바꿉니다.

나는 커피를 마셨다.
→ ["나는", " 커피", "를", " 마셨", "다", "."]
→ [1024, 8153, 317, 22019, 91, 13]

여기서 ID 1024가 숫자 1024라는 크기 자체로 의미를 갖는 것은 아닙니다. 1024가 317보다 더 크다고 해서 더 중요한 토큰이라는 뜻도 아닙니다. 토큰 ID는 일종의 주소입니다. "이 번호에 해당하는 토큰을 찾아라"라는 표지판에 가깝습니다.

이 주소를 통해 다음 장에서 배울 임베딩으로 이동합니다. 임베딩은 각 토큰 ID를 모델이 계산할 수 있는 벡터로 바꿔주는 단계입니다. 그러므로 지금 단계에서 기억할 것은 이렇습니다.

텍스트는 토큰이 되고, 토큰은 ID가 되며, ID는 곧 임베딩을 찾기 위한 주소가 됩니다.

어휘 사전에 없는 단어는 어떻게 할까

LLM이 실제 세상에서 쓸모 있으려면 처음 보는 단어도 처리할 수 있어야 합니다. 새 상품명, 사람 이름, 오타, 이모지, 프로그래밍 코드, 외국어 표현은 언제든 등장합니다. 어휘 사전에 정확히 같은 단어가 없다고 해서 모델이 매번 멈춰버리면 곤란합니다.

하위 단어 토큰화는 이 문제를 작은 조각으로 해결합니다.

예를 들어 "초거대언어모델스럽다"라는 표현이 어휘 사전에 통째로 없다고 해봅시다. 그래도 다음처럼 나눌 수 있습니다. 실제 토큰화 결과는 모델마다 다르므로, 아래는 이해를 위한 가상의 예입니다.

초거대언어모델스럽다
→ [초] [거대] [언어] [모델] [스럽] [다]

모델은 "초", "거대", "언어", "모델" 같은 조각을 이미 학습 중에 많이 봤을 수 있습니다. 그러면 통째 단어를 처음 보더라도 어느 정도 의미의 실마리를 잡을 수 있습니다.

영어에서도 비슷합니다.

unbelievably
→ [un] [believ] [ably]

또는 전문 용어가 다음처럼 쪼개질 수 있습니다.

tokenization
→ [token] [ization]

물론 항상 사람이 보기 좋게 나뉘지는 않습니다. 어떤 모델은 한글 한 글자 단위에 가깝게 나눌 수 있고, 어떤 모델은 바이트 수준의 조각을 사용할 수도 있습니다. 최신 토크나이저 중에는 거의 모든 텍스트를 바이트 단위 조합으로 표현할 수 있게 설계된 것도 있습니다. 이런 방식은 "완전히 모르는 문자"를 줄이는 데 유리합니다.

반면 일부 토크나이저는 정말 표현하기 어려운 입력을 특별한 미지 토큰으로 바꿀 수 있습니다. 보통 이런 토큰은 [UNK]처럼 표시됩니다. [UNK]는 "unknown", 즉 알 수 없다는 뜻입니다. 하지만 많은 현대 LLM은 가능한 한 [UNK]에 의존하지 않고, 더 작은 단위로 쪼개서 표현하려고 합니다. 모르는 단어를 통째로 버리는 것보다, 작은 단서라도 남기는 편이 낫기 때문입니다.

공백도 토큰이다

토큰화를 처음 볼 때 많은 사람이 놀라는 지점이 있습니다. 바로 공백입니다.

사람은 공백을 단어 사이의 빈칸으로만 생각합니다. 하지만 토크나이저에게 공백은 중요한 문자입니다. 특히 영어권 데이터에서는 단어 앞의 공백까지 포함한 토큰이 자주 등장합니다.

예를 들어 가상의 토큰 목록이 다음과 같을 수 있습니다.

"apple"
" apple"
"the"
" the"
"ing"
"ing "

여기서 "apple"과 " apple"은 서로 다른 토큰일 수 있습니다. 앞의 공백이 포함되었는지가 다르기 때문입니다. 문장 중간에 등장하는 단어는 대개 앞에 공백이 있으므로 " apple" 같은 토큰이 자주 쓰입니다. 반대로 문장 맨 앞의 "apple"은 앞에 공백이 없으므로 다른 토큰으로 처리될 수 있습니다.

이것이 이상해 보일 수 있지만, 모델 입장에서는 효율적입니다. 실제 텍스트에서는 "단어만" 등장하는 것이 아니라 "공백+단어" 패턴이 매우 자주 등장합니다. 자주 등장하는 패턴을 하나의 토큰으로 묶으면 전체 토큰 수를 줄일 수 있습니다.

예를 들어 영어 문장에서 " the", " of", " and" 같은 조각은 매우 자주 나옵니다. 이런 조각을 하나의 토큰으로 처리하면 긴 문서를 더 짧은 토큰 시퀀스로 표현할 수 있습니다.

한국어에서도 공백은 중요합니다. 다만 한국어는 조사와 어미가 단어에 붙고, 띄어쓰기 습관도 사람마다 조금씩 다릅니다. 그래서 공백이 어디에 있느냐에 따라 토큰화 결과가 달라질 수 있습니다.

인공지능을공부한다
인공지능을 공부한다
인공지능 을 공부한다

사람은 세 문장을 비슷하게 이해할 수 있지만, 토크나이저는 서로 다른 조각으로 나눌 수 있습니다. 프롬프트를 쓸 때 띄어쓰기가 완전히 사소한 문제가 아닌 이유가 여기에 있습니다. 띄어쓰기를 조금 바꾼다고 모델의 의미 이해가 매번 크게 바뀌는 것은 아니지만, 토큰 수와 내부 표현에는 차이가 생길 수 있습니다.

자주 등장하는 표현은 더 싸게 처리된다

토큰화는 비용과도 연결됩니다. LLM API를 사용할 때 비용은 보통 입력 토큰 수와 출력 토큰 수에 따라 계산됩니다. 즉, 같은 내용을 전달하더라도 토큰 수가 많으면 더 비싸고, 처리 시간도 늘어날 수 있습니다.

그런데 토큰 수는 글자 수와 정확히 같지 않습니다. 어떤 표현은 짧아도 여러 토큰으로 쪼개질 수 있고, 어떤 표현은 길어 보여도 하나나 둘의 토큰으로 처리될 수 있습니다.

자주 등장하는 표현은 어휘 사전에 큰 조각으로 들어갈 가능성이 높습니다. 그래서 효율적으로 인코딩됩니다. 반대로 드문 고유명사, 복잡한 코드, 특수기호가 많은 문자열은 여러 토큰으로 나뉠 수 있습니다.

예를 들어 다음 두 문자열을 생각해봅시다.

오늘 날씨 어때?
XqJ_19zz@@@_alpha_final_v7

두 번째 문자열이 글자 수만 놓고 압도적으로 길지 않더라도, 토큰화 결과는 훨씬 복잡할 수 있습니다. 무작위에 가까운 문자열은 자주 등장한 패턴이 아니기 때문에 작은 조각으로 쪼개질 가능성이 큽니다.

코드를 다룰 때도 비슷합니다. 프로그래밍 언어의 예약어와 흔한 패턴은 잘 처리되지만, 긴 변수명, 난수처럼 생긴 식별자, 압축된 JSON, 긴 Base64 문자열은 토큰 수를 크게 늘릴 수 있습니다. LLM으로 개발 도구를 만들 때 입력을 무작정 많이 넣으면 비용이 빠르게 증가하는 이유입니다.

토큰화는 모델의 "읽기 단가"를 결정하는 첫 관문입니다.

한국어는 왜 토큰 수가 늘어날 수 있을까

한국어 사용자는 종종 영어보다 한국어 입력의 토큰 수가 더 많이 나오는 경험을 합니다. 모든 모델에서 항상 그렇다고 단정할 수는 없지만, 이런 일이 생기는 이유는 분명합니다.

한국어는 형태 변화가 풍부한 언어입니다. 명사 뒤에 조사가 붙고, 동사와 형용사에는 어미가 다양하게 붙습니다.

먹다
먹었다
먹었습니다
먹었겠지만
먹어버렸는데요

사람에게는 모두 "먹다"와 관련된 표현으로 보입니다. 하지만 텍스트 표면만 보면 꽤 다른 문자열입니다. 토크나이저가 이를 얼마나 잘게 나눌지는 학습 데이터와 어휘 구성에 따라 달라집니다.

가상의 예를 보겠습니다.

먹었겠지만
→ [먹] [었] [겠] [지만]

또는 다른 토크나이저에서는 이렇게 나뉠 수도 있습니다.

먹었겠지만
→ [먹었] [겠지만]

어떤 방식이든 영어의 흔한 단어 하나가 한 토큰으로 처리되는 경우와 비교하면, 한국어 표현 하나가 여러 토큰으로 나뉠 가능성이 있습니다.

또 하나의 이유는 학습 데이터의 분포입니다. 어떤 토크나이저가 영어 데이터 중심으로 학습되었다면 영어의 흔한 단어와 표현은 큰 토큰으로 많이 등록됩니다. 반면 한국어 표현은 상대적으로 더 작은 조각으로 남을 수 있습니다. 최근의 다국어 모델은 이런 문제를 줄이기 위해 다양한 언어 데이터를 사용하지만, 토큰화 효율의 차이는 여전히 체감될 수 있습니다.

한국어에는 띄어쓰기 문제도 있습니다. 한국어는 의미 단위와 띄어쓰기 단위가 항상 깔끔하게 일치하지 않습니다. 사람마다 띄어쓰기 습관도 조금씩 다릅니다.

할 수 있다
할수 있다
할 수있다
할수있다

사람은 네 표현을 모두 이해할 수 있지만, 토크나이저는 서로 다른 토큰 시퀀스로 바꿀 수 있습니다. 그래서 한국어 프롬프트를 설계할 때는 문장을 지나치게 압축하기보다 자연스럽고 일관된 띄어쓰기를 유지하는 편이 좋습니다.

토큰 수는 컨텍스트 윈도우를 잡아먹는다

LLM에는 한 번에 읽고 쓸 수 있는 최대 토큰 수가 있습니다. 이를 컨텍스트 윈도우라고 합니다. 다음 장에서 더 자세히 다루겠지만, 지금은 "모델의 작업대 크기"라고 생각하면 됩니다.

작업대가 8,000토큰이라면 입력과 출력, 시스템 지시문, 대화 기록, 문서 내용이 모두 그 안에 들어가야 합니다. 사용자가 입력한 질문만 계산하는 것이 아닙니다. 이전 대화, 개발자가 넣어둔 안내문, RAG로 검색해 붙인 문서 조각까지 모두 토큰을 차지합니다.

여기서 토큰화가 중요해집니다. 같은 분량의 문서라도 토큰화 결과에 따라 컨텍스트 윈도우를 더 많이 차지할 수 있습니다. 특히 한국어 문서, 코드, 표, 숫자와 특수기호가 많은 데이터는 예상보다 많은 토큰을 사용할 수 있습니다.

컨텍스트 윈도우가 커지면 더 많은 정보를 넣을 수 있지만, 무조건 좋은 것은 아닙니다. 입력이 길수록 비용이 늘고, 응답 속도가 느려질 수 있습니다. 전통적인 Attention 구조에서는 토큰 수가 늘어날수록 서로 비교해야 하는 관계도 크게 늘어납니다. 현대 모델들은 여러 최적화를 사용하지만, 긴 입력이 더 많은 계산을 요구한다는 큰 방향은 변하지 않습니다.

그러므로 LLM 애플리케이션을 만들 때는 글자 수가 아니라 토큰 수를 기준으로 생각해야 합니다. "A4 한 장이면 괜찮겠지"가 아니라 "이 문서가 몇 토큰인가?"를 확인해야 합니다.

이상한 답변 뒤에 토큰화가 숨어 있을 때

LLM은 가끔 사람이 보기에는 간단해 보이는 일을 이상하게 틀립니다. 예를 들어 단어의 글자 수를 세거나, 특정 글자가 몇 번 나오는지 묻거나, 문자열을 거꾸로 뒤집는 작업에서 실수할 때가 있습니다.

그 이유 중 하나가 토큰화입니다.

사람은 "banana"를 글자 여섯 개로 봅니다.

b a n a n a

하지만 모델은 꼭 이렇게 보지 않을 수 있습니다. 토크나이저에 따라 "banana"가 하나의 토큰이 될 수도 있고, 여러 조각이 될 수도 있습니다. 모델이 내부에서 직접 다루는 기본 단위는 글자가 아니라 토큰입니다. 그래서 글자 단위의 정밀한 조작은 생각보다 까다로울 수 있습니다.

한국어도 마찬가지입니다. "안녕하세요"를 사람이 보기에는 다섯 글자로 쉽게 셀 수 있지만, 모델 내부에서는 다른 단위로 나뉠 수 있습니다. 심지어 이모지나 결합 문자가 들어가면 사람 눈에 하나처럼 보이는 기호가 내부적으로 여러 코드 포인트나 바이트로 표현될 수 있습니다.

물론 LLM이 이런 작업을 절대 못한다는 뜻은 아닙니다. 충분히 큰 모델은 많은 패턴을 학습했고, 단계적으로 생각하게 하면 잘 맞히는 경우도 많습니다. 하지만 "모델이 글자를 직접 보고 있지 않다"는 사실을 알면, 왜 사소해 보이는 문자열 작업에서 실수가 나오는지 이해하기 쉬워집니다.

그래서 제품을 만들 때는 이런 판단이 중요합니다. 글자 수 계산, 정규식 검사, JSON 검증, 날짜 계산처럼 정확한 규칙이 필요한 일은 LLM에게 감으로 맡기기보다 코드로 처리하는 편이 안전합니다. LLM은 언어적 판단과 유연한 해석에 강하고, 계산기나 파서가 해야 할 일은 도구에 맡기는 것이 좋습니다. 훗날 Function Calling과 Tool Use를 배울 때 이 원칙이 다시 등장합니다.

RAG 청킹에도 토큰화가 영향을 준다

토큰화는 RAG에서도 매우 중요합니다. RAG는 외부 문서를 검색해서 모델에게 함께 넣어주는 방식입니다. 이때 문서를 적당한 크기의 조각으로 나누는 과정을 흔히 청킹이라고 합니다.

문서를 조각낼 때 많은 사람이 처음에는 글자 수나 문단 수를 기준으로 생각합니다.

문서 하나를 1,000자씩 자르자.

나쁘지 않은 출발점이지만, LLM 입장에서는 1,000자가 중요한 단위가 아닙니다. 중요한 것은 토큰 수입니다. 1,000자짜리 영어 문서와 1,000자짜리 한국어 문서, 1,000자짜리 코드 파일은 서로 다른 토큰 수를 가질 수 있습니다.

RAG에서 청크가 너무 크면 컨텍스트 윈도우를 많이 차지합니다. 검색된 청크 몇 개만 넣어도 입력이 가득 차고, 정작 사용자의 질문이나 답변 공간이 부족해질 수 있습니다. 반대로 청크가 너무 작으면 필요한 문맥이 잘려나갑니다. 답변에 필요한 근거가 두 청크에 나뉘어 있는데 하나만 검색되면 모델은 불완전한 정보를 보게 됩니다.

예를 들어 제품 설명서에 이런 문장이 있다고 해봅시다.

환불은 구매 후 7일 이내에만 가능하다. 단, 다운로드를 시작한 디지털 상품은 환불 대상에서 제외된다.

청킹이 잘못되어 앞문장과 뒷문장이 분리되면, 모델은 "7일 이내 환불 가능"이라는 정보만 보고 잘못된 답을 할 수 있습니다. 토큰 수만 맞추는 것도 중요하지만, 의미 단위가 끊기지 않도록 자르는 것도 중요합니다.

실전에서는 보통 다음 요소를 함께 고려합니다.

  • 청크 하나의 최대 토큰 수

  • 청크 사이의 겹침 구간, 즉 오버랩

  • 문단, 제목, 표, 목록 같은 문서 구조

  • 질문에 필요한 근거가 한 청크 안에 들어올 가능성

토큰화는 이 모든 결정의 바닥에 있습니다. 결국 모델에 들어가는 것은 문서의 페이지 수가 아니라 토큰 시퀀스이기 때문입니다.

프롬프트 설계에서도 토큰화를 의식해야 한다

프롬프트 엔지니어링은 멋진 문구를 쓰는 기술이 아닙니다. 모델에게 필요한 정보를 적절한 구조와 적절한 길이로 전달하는 기술입니다. 이때도 토큰화 감각이 필요합니다.

예를 들어 같은 의미라도 지나치게 장황한 프롬프트는 많은 토큰을 사용합니다. 반대로 너무 압축한 프롬프트는 의미가 모호해질 수 있습니다. 좋은 프롬프트는 짧기만 한 문장이 아니라, 모델이 오해하지 않을 만큼 충분하고 불필요한 반복이 적은 문장입니다.

프롬프트를 쓸 때는 다음 습관이 도움이 됩니다.

  • 긴 배경 설명은 필요한 부분만 남기고 줄입니다.

  • 문서나 예시는 구분자를 사용해 경계를 분명히 합니다.

  • 출력 형식은 말로 길게 설명하기보다 예시나 구조로 보여줍니다.

  • 대화 기록을 무한히 누적하지 말고 필요한 범위만 유지합니다.

  • 글자 수가 아니라 토큰 수를 기준으로 입력 길이를 확인합니다.

특히 RAG나 에이전트처럼 여러 정보를 조합하는 시스템에서는 토큰 예산을 관리해야 합니다. 시스템 메시지, 사용자 질문, 검색 문서, 도구 호출 결과, 이전 대화가 모두 같은 컨텍스트 윈도우 안에서 경쟁합니다. 중요한 정보가 뒤로 밀리거나 잘리면 모델의 답변 품질이 떨어집니다.

토큰화는 눈에 보이지 않는 예산표입니다. 좋은 LLM 서비스는 이 예산을 낭비하지 않습니다.

토큰화 결과는 모델마다 다르다

여기까지 여러 예시를 보았지만, 한 가지를 꼭 기억해야 합니다. 토큰화 결과는 모델마다 다릅니다.

같은 문장이라도 어떤 모델은 다음처럼 나눌 수 있습니다.

나는 언어 모델을 공부한다
→ [나는] [ 언어] [ 모델] [을] [ 공부] [한다]

다른 모델은 이렇게 나눌 수 있습니다.

나는 언어 모델을 공부한다
→ [나] [는] [ 언] [어] [ 모델] [을] [ 공부] [한다]

또 다른 모델은 공백이나 바이트 처리 방식 때문에 전혀 다른 조합을 만들 수도 있습니다. 따라서 실전에서 특정 모델을 사용한다면 그 모델의 토크나이저로 직접 확인하는 것이 가장 정확합니다.

이 차이는 단순한 호기심이 아닙니다. 같은 입력을 넣었는데 모델 A에서는 500토큰, 모델 B에서는 700토큰이 될 수 있습니다. API 비용과 속도, 컨텍스트 사용량이 달라집니다. 다국어 서비스를 만들 때는 이 차이가 더 중요해집니다.

특히 제품을 운영할 때 모델을 교체한다면, 답변 품질만 볼 것이 아니라 토큰화 효율도 함께 봐야 합니다. 어떤 모델은 한국어를 더 자연스럽게 처리하지만 토큰 수가 많을 수 있고, 어떤 모델은 비용 면에서 유리할 수 있습니다. 모델 선택은 정확도, 속도, 비용, 언어 지원을 함께 보는 문제입니다.

작은 실험: 토큰을 상상해보기

이제 짧은 문장을 하나 놓고 토큰화를 상상해봅시다.

ChatGPT로 한국어 문서를 요약해줘.

사람은 이 문장을 대략 다음 의미 단위로 볼 수 있습니다.

ChatGPT / 로 / 한국어 / 문서 / 를 / 요약 / 해줘 / .

하지만 실제 토크나이저는 다르게 나눌 수 있습니다. 가상의 예시는 다음과 같습니다.

[Chat] [GPT] [로] [ 한국] [어] [ 문서] [를] [ 요약] [해] [줘] [.]

또는 어떤 토크나이저에서는 "ChatGPT"가 하나의 토큰일 수도 있고, "한국어"가 하나의 토큰일 수도 있습니다. 반대로 더 잘게 쪼개질 수도 있습니다.

여기서 관찰할 점은 세 가지입니다.

첫째, 영어와 한국어가 섞이면 토큰화 방식도 섞입니다. 둘째, 조사와 어미는 별도 토큰이 되거나 앞말에 붙어 처리될 수 있습니다. 셋째, 공백이 포함된 토큰이 등장할 수 있습니다.

이 감각이 생기면 LLM의 입력을 바라보는 눈이 달라집니다. 이제 프롬프트는 그냥 문장이 아니라 토큰들의 행렬로 보이기 시작합니다.

토큰화는 사소한 전처리가 아니다

토큰화는 언뜻 보면 모델에 들어가기 전의 준비 작업처럼 보입니다. 하지만 실제로는 LLM의 동작 방식에 깊게 영향을 미칩니다.

토큰화가 달라지면 같은 문장도 다른 숫자열이 됩니다. 다른 숫자열은 다른 임베딩으로 이어지고, 다른 임베딩은 모델 내부의 계산 경로를 바꿉니다. 물론 큰 모델은 다양한 표현을 견디도록 학습되어 있지만, 출발점이 달라진다는 사실은 변하지 않습니다.

토큰화를 이해하면 다음과 같은 질문에 답할 수 있습니다.

  • 왜 같은 글자 수인데 어떤 입력은 더 비쌀까?

  • 왜 한국어 문서가 예상보다 많은 컨텍스트를 차지할까?

  • 왜 LLM이 글자 수 세기나 문자열 뒤집기에서 실수할까?

  • 왜 RAG에서 청크 크기를 토큰 기준으로 잡아야 할까?

  • 왜 모델을 바꾸면 비용과 응답 품질이 함께 달라질까?

이 질문들은 모두 실제 서비스 개발에서 만나는 문제입니다. 토큰화는 이론의 구석에 있는 작은 주제가 아니라, LLM을 제품에 붙일 때 매일 마주치는 현실적인 변수입니다.

다음 단계: ID는 어떻게 의미가 되는가

지금까지 우리는 텍스트가 토큰으로 나뉘고, 토큰이 어휘 사전의 ID로 바뀌는 과정을 살펴보았습니다. 하지만 아직 중요한 질문이 남아 있습니다.

토큰 ID는 그저 번호일 뿐입니다. 1024라는 숫자 자체에는 "나는"이라는 의미가 들어 있지 않습니다. 그렇다면 모델은 어떻게 이 번호를 의미 있는 정보로 바꿀까요?

그 답이 바로 임베딩입니다.

임베딩은 토큰 ID를 벡터로 바꾸는 과정입니다. 벡터는 모델이 계산할 수 있는 숫자 묶음입니다. 다음 장에서는 이 벡터가 어떻게 단어와 문장의 의미를 담는지, 그리고 왜 "왕 - 남자 + 여자 ≈ 여왕" 같은 유명한 예시가 가능해지는지 살펴보겠습니다.

토큰화가 텍스트를 모델의 입구까지 데려왔다면, 임베딩은 그 텍스트를 모델이 이해할 수 있는 공간 안으로 들여보내는 문입니다.

책 목록

처음 만나는 LLM: 원리부터 Mini GPT 구현, RAG 서비스 개발까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기