Command Palette

Search for a command to run...

1편에서는 LLM을 검색기가 아니라 다음 token 생성기로 다시 잡았다.

이번 글에서는 그 첫 단계로 내려간다. 사용자가 쓴 문장은 곧바로 Transformer block으로 들어가지 않는다. 먼저 tokenizer를 지나 token ID가 되고, token ID는 embedding table을 지나 벡터가 된다. 그리고 모델이 답을 만들 때는 다시 LM head를 통해 vocabulary 전체의 token 후보로 돌아온다.

이 경로가 중요한 이유는 단순하다.

토큰은 모델의 입력 단위이면서, 사용자가 체감하는 비용 단위다.

AI Frontier EP 94에서 tokenizer 변화가 화제가 된 것도 이 지점 때문이었다. vocabulary 숫자가 줄어든 것 같고, 같은 작업을 했는데 token이 더 많이 쓰이는 느낌이 있었다. 그 논의는 공식 스펙 확정이라기보다 사용자 체감과 외부 분석에 가까웠다. 하지만 질문 자체는 LLM을 이해하는 데 아주 좋다.

같은 문장이 왜 더 많은 token이 될 수 있을까?
vocabulary가 줄면 모델은 작아지는가?
embedding과 LM head는 tokenizer와 어떻게 연결되는가?
토큰 수가 늘면 왜 KV cache와 추론 비용까지 이어지는가?

이 글의 목표는 이 질문을 한 줄로 연결하는 것이다.

1편이 전체 지도를 만들었다면, 2편은 그 지도에서 가장 먼저 돈이 새는 입구를 보는 글이다. LLM 비용은 GPU, KV cache, batch scheduling 같은 뒷단에서만 결정되지 않는다. 사용자의 문장이 어떤 token sequence가 되는지부터 이미 비용 구조가 시작된다.

그래서 이 글에서는 tokenizer를 "문자열을 자르는 도구"가 아니라 "vocabulary, embedding table, LM head, sequence length, KV cache를 한꺼번에 건드리는 경계"로 본다.

Token은 단어가 아니다

LLM을 처음 배울 때 token을 단어라고 부르면 편하다. 하지만 정확히는 다르다.

token은 모델의 vocabulary에 등록된 텍스트 조각이다. 어떤 token은 단어 하나처럼 보일 수 있다. 어떤 token은 단어의 앞부분이나 뒷부분일 수 있다. 어떤 token은 공백, 줄바꿈, 괄호, 코드 조각, 한글 음절 또는 여러 글자의 묶음일 수 있다.

예를 들어 영어 문장에서는 이런 일이 일어날 수 있다.

"empowers"
-> "em" + "powers"

한 단어가 두 token이 된다. 반대로 아주 자주 나오는 단어는 하나의 token일 수 있다. 코드에서는 괄호, 들여쓰기, snake_case 일부, common keyword가 token 경계에 영향을 준다.

한글이나 CJK 언어는 영어와 다른 방식으로 쪼개질 수 있다. 어떤 tokenizer는 한글을 더 촘촘하게 자르고, 어떤 tokenizer는 자주 나오는 한글 조합을 더 큰 단위로 가진다. 그래서 "글자 수"와 "token 수"는 같은 말이 아니다.

이 차이는 사용자에게 곧바로 비용으로 보인다.

같은 문장
-> token 100개

같은 문장
-> token 140개

문장의 의미가 같아도 모델이 처리하는 입력 길이는 달라진다. LLM API나 코딩 에이전트에서 token 기준 과금, context 제한, cache 비용이 걸려 있다면 이 차이는 실제 비용 차이가 된다.

그래서 tokenizer는 단순 전처리기가 아니다. LLM 비용의 첫 관문이다.

Vocabulary는 모델이 알고 있는 token 후보 목록이다

tokenizer는 문장을 token으로 쪼갠 뒤, 각 token을 ID로 바꾼다.

"hello" -> 15339
" world" -> 1917

여기서 ID는 일련번호다. 153391917보다 의미상 더 크거나 더 중요하다는 뜻이 아니다. 모델이 vocabulary 안에서 해당 token을 구분하기 위한 번호다.

vocabulary size는 이 token 후보가 몇 개인지를 뜻한다.

vocabulary size = 모델이 구분하는 token 종류 수

vocabulary가 50,000이라면 모델은 대략 5만 개 token 후보를 알고 있다. 다음 token을 고를 때도 이 후보 전체에 대한 점수를 만든다. 사용자가 넣은 문장을 쪼갤 때도 이 후보 목록을 기준으로 나눈다.

여기서 직관적인 착각이 생긴다.

vocabulary가 크면 더 좋고,
vocabulary가 작으면 더 나쁜가?

꼭 그렇지는 않다.

vocabulary가 크면 자주 나오는 긴 조각을 하나의 token으로 처리할 가능성이 커진다. 같은 텍스트가 더 짧은 token sequence가 될 수 있다. 하지만 embedding table과 LM head의 row 수도 커진다.

vocabulary가 작으면 embedding table과 LM head는 작아질 수 있다. 대신 같은 텍스트가 더 잘게 쪼개져 sequence length가 늘 수 있다.

즉 vocabulary는 모델 크기와 sequence length 사이의 trade-off를 만든다.

큰 vocabulary
-> token 수가 줄 수 있음
-> embedding / LM head row 수는 커짐

작은 vocabulary
-> embedding / LM head row 수는 줄 수 있음
-> 같은 텍스트가 더 많은 token으로 쪼개질 수 있음

어느 쪽이 항상 이긴다고 말하기 어렵다. 언어 분포, 코드 비중, 학습 데이터, 모델 크기, serving 비용, 사용자 workload에 따라 판단이 달라진다.

Token ID는 아직 의미가 아니다

token ID가 만들어졌다고 해서 모델이 의미를 이해한 것은 아니다.

ID는 번호표다. 모델은 그 번호표를 직접 계산하지 않는다. 15339라는 숫자 하나를 벡터처럼 곱하고 더해서 의미를 만드는 것이 아니다.

그래서 embedding table이 필요하다.

embedding table은 vocabulary의 각 token ID에 대응하는 벡터를 담은 큰 표다.

token ID 15339
-> embedding table lookup
-> [0.12, -0.08, 0.44, ..., 0.03]

이 벡터가 Transformer block으로 들어가는 실제 입력이다.

입문 단계에서는 embedding을 "token ID를 모델 내부 좌표로 바꾸는 층"이라고 이해하면 충분하다. 같은 token ID는 같은 embedding row에서 출발한다. 하지만 Transformer block을 지나면서 그 벡터는 문맥에 따라 계속 바뀐다.

예를 들어 bank라는 token이 있다고 하자.

"river bank"
"bank account"

처음 embedding row는 같을 수 있다. 하지만 attention과 MLP를 지나면서 앞뒤 문맥이 섞이고, 최종 hidden state는 달라진다. 그래서 embedding은 "완성된 의미"라기보다 "계산을 시작하기 위한 초기 좌표"에 가깝다.

여기에 위치 정보도 필요하다.

LLM은 token들의 순서를 알아야 한다. 같은 token이라도 앞에 있느냐 뒤에 있느냐에 따라 역할이 달라진다. 고전 Transformer 설명에서는 positional encoding을 embedding에 더한다고 말하고, 현대 decoder-only 모델에서는 RoPE 같은 방식으로 attention 계산 안에서 위치 정보를 다루는 경우가 많다.

지금은 이렇게 정리하면 된다.

token ID = vocabulary 안의 번호
embedding = 그 번호를 벡터로 바꾸는 입력층
position information = token 순서를 반영하는 장치

이 세 가지가 합쳐져야 모델이 문장을 계산할 수 있다.

Embedding table은 모델 weight다

embedding table은 단순 캐시가 아니다. 모델의 parameter다.

학습 중에는 embedding row도 업데이트된다. 특정 token이 어떤 벡터에서 출발해야 다음 token 예측에 도움이 되는지 학습되는 것이다. 추론 때는 이 table이 고정된 weight로 GPU memory에 올라간다.

그래서 vocabulary size는 embedding table 크기에 직접 영향을 준다.

embedding table size
= vocabulary size x hidden dimension

vocabulary가 50,000이고 hidden dimension이 4,096이라면 embedding table은 50,000개의 row와 4,096차원 벡터를 가진다. 숫자는 예시지만 구조는 이렇다.

vocabulary가 커지면 embedding table이 커진다. vocabulary가 줄면 embedding table row 수는 줄 수 있다.

그러나 이것만 보고 "vocabulary를 줄이면 비용이 줄어든다"고 말하면 반쪽 설명이다.

embedding table이 작아져도 같은 텍스트가 더 많은 token으로 쪼개지면 sequence length가 길어진다. sequence length가 길어지면 prefill 계산량, attention이 보는 위치 수, KV cache 크기, decode 반복이 영향을 받는다.

비용은 한 곳에서 사라지지 않는다. 이동한다.

LM head는 벡터를 다시 vocabulary 후보로 돌려보낸다

입력 쪽에 embedding table이 있다면, 출력 쪽에는 LM head가 있다.

Transformer block stack을 지나면 마지막 위치의 hidden state가 나온다. 이 hidden state는 아직 글자가 아니다. 모델은 이 벡터를 보고 다음에 올 token 후보들의 점수를 만들어야 한다.

그 역할을 하는 층이 LM head다.

last hidden state
-> LM head
-> vocabulary 전체에 대한 logits
-> sampling / decoding
-> selected token ID

Vocabulary, embedding table, and LM head dimensions

Vocabulary size는 입력 쪽 embedding table의 row 수와 출력 쪽 LM head의 logits 차원을 함께 잡는다.

logits는 확률로 바꾸기 전의 점수다. vocabulary에 token 후보가 50,000개라면 다음 token 후보 50,000개에 대한 점수가 나온다. 그 점수에 softmax, temperature, top-k, top-p 같은 decoding 전략이 적용되고, 최종 token 하나가 선택된다.

여기서 LM head를 "사전에서 token을 찍는 포인터"로 이해하면 조금 빗나간다. 더 정확히는 마지막 hidden state와 vocabulary의 각 출력 벡터가 얼마나 잘 맞는지 점수를 매기는 선형 projection이다.

final hidden state
-> token 0 점수
-> token 1 점수
-> token 2 점수
...
-> vocabulary 전체 점수

선택된 token ID는 이 점수 분포에서 하나를 고른 결과다. 마지막 hidden state 자체가 다음 token의 입력 vector가 되는 것은 아니다. hidden state는 "다음 token이 무엇일지 판단하기 위한 문맥 표현"이고, 선택된 token ID는 다음 step에서 다시 embedding table을 통해 입력 vector가 된다.

이 token이 다시 문맥 뒤에 붙는다.

selected token ID
-> text piece
-> context에 추가
-> 다음 decode step에서 다시 embedding

이것이 autoregressive 생성이다.

중요한 점은 LM head도 vocabulary와 연결된다는 것이다.

LM head output dimension = vocabulary size

vocabulary가 바뀌면 입력 쪽 embedding뿐 아니라 출력 쪽 LM head의 모양도 영향을 받는다. 실제 모델 구현에서는 embedding weight와 LM head weight를 묶어 쓰는 weight tying이 쓰일 수 있다. 모든 모델이 반드시 같은 방식이라는 뜻은 아니지만, tokenizer와 vocabulary가 모델의 입구와 출구를 동시에 잡는다는 점은 변하지 않는다.

EP 94에서 tokenizer 변화와 embedding, LM head가 함께 언급된 이유가 여기에 있다. tokenizer는 모델 앞단만의 문제가 아니다. 모델이 어떤 token 후보를 입력으로 받고, 어떤 token 후보로 출력할지를 함께 정한다.

Vocabulary가 줄면 정말 싸지는가

이제 EP 94의 tokenizer 논의를 다시 볼 수 있다.

대화의 핵심은 대략 이랬다.

vocabulary 숫자가 줄어든 것 같다
-> 같은 텍스트가 더 많은 token으로 쪼개지는 것 같다
-> 사용자 입장에서는 token 비용이 오른 것처럼 느껴진다

여기서 중요한 것은 "공식적으로 정확히 몇 배 올랐다"가 아니다. 숫자보다 구조가 중요하다.

vocabulary를 줄이면 다음 효과가 가능하다.

embedding table row 수 감소
LM head output row 수 감소

하지만 동시에 다음 효과도 가능하다.

같은 prompt의 token 수 증가
sequence length 증가
prefill workload 증가
KV cache memory 증가
decode step의 context 길이 증가

사용자에게는 두 번째 효과가 더 직접적으로 보인다. API나 코딩 에이전트는 token 단위로 사용량을 보여주기 때문이다.

예를 들어 같은 코드 리뷰 요청을 했는데 이전에는 20,000 token이었고 이후에는 28,000 token이 되었다고 하자. 모델 내부에서 embedding table이 조금 작아졌는지는 사용자가 체감하기 어렵다. 하지만 context window 사용량, 요금, rate limit, session budget은 바로 체감된다.

그래서 vocabulary 축소는 "무조건 비용 절감"이 아니다.

모델 provider 관점:
embedding / LM head parameter와 serving 최적화의 선택지

사용자 관점:
같은 작업이 몇 token으로 계산되는지의 문제

두 관점이 다르다.

Token 수 증가는 KV cache로 이어진다

token 수가 늘면 왜 KV cache까지 영향을 받을까?

1편에서 봤듯이 prefill은 prompt 전체를 Transformer block stack에 통과시키며 각 layer의 Key/Value를 만든다. 이 Key/Value가 KV cache다.

token이 많을수록 저장해야 할 K/V도 많다.

KV cache 규모
~ sequence length x layer 수 x attention 관련 차원

정확한 크기는 모델 architecture, head 수, KV head 수, precision, cache 구현에 따라 달라진다. 하지만 방향은 분명하다. sequence length가 길어지면 KV cache는 커진다.

tokenizer가 같은 텍스트를 더 잘게 쪼개면 sequence length가 늘 수 있다. 그러면 prefill 단계에서 더 많은 token을 처리해야 하고, 이후 decode에서도 더 긴 past context를 가진 상태로 다음 token을 만든다.

이것은 특히 코딩 에이전트에서 크게 느껴진다.

Claude Code나 Codex 같은 도구는 대화 몇 줄만 보내지 않는다. repository context, diff, 파일 일부, instruction, 이전 대화, tool result가 함께 들어간다. 이 전체가 token sequence가 된다.

긴 CLAUDE.md
+ 긴 코드 파일
+ 긴 tool output
+ 이전 대화
-> 긴 prompt token
-> 큰 prefill
-> 큰 KV cache

따라서 tokenizer는 비용표 맨 앞에 있지만, 실제로는 추론 인프라 전체에 파급된다.

"토큰 단가가 올랐다"는 말의 정확한 의미

사용자 입장에서 "토큰 단가가 올랐다"고 느끼는 상황은 두 가지로 나눌 수 있다.

첫째, provider가 실제 price per token을 올린 경우다.

둘째, price per token은 같아도 같은 작업이 더 많은 token으로 계산되는 경우다.

EP 94의 tokenizer 논의는 후자에 가까운 문제의식이었다. 같은 요청을 했는데 tokenizer 변화 때문에 token count가 늘면, 사용자 체감 비용은 올라간다.

요금 = token 수 x token당 가격

token당 가격이 그대로여도 token 수가 늘면 총액은 증가한다.

이 차이를 모르면 모델 가격표를 잘못 읽게 된다.

예를 들어 두 모델이 같은 1M token 가격을 제시한다고 해도, 같은 한국어 문서, 영어 문서, 코드 파일을 각각 몇 token으로 쪼개는지에 따라 실제 비용은 달라질 수 있다. 특히 코드, 긴 markdown, JSON, 로그, mixed-language text는 tokenizer에 민감하다.

그래서 LLM 비용을 볼 때는 최소한 세 축을 함께 봐야 한다.

1. price per input/output token
2. tokenizer가 내 workload를 몇 token으로 만드는지
3. cache, context, reasoning token, tool call이 추가로 만드는 token

이 세 축을 분리해야 "비싸졌다"는 감각을 정확히 설명할 수 있다.

Embedding과 LM head는 입구와 출구다

이제 전체 경로를 다시 묶어 보자.

text
-> tokenizer
-> token IDs
-> embedding table
-> Transformer blocks
-> final hidden state
-> LM head
-> logits over vocabulary
-> selected next token

tokenizer는 text를 token ID로 바꾼다. embedding table은 token ID를 벡터로 바꾼다. Transformer block은 그 벡터를 문맥에 맞게 계속 갱신한다. LM head는 마지막 벡터를 다시 vocabulary 전체의 token 후보 점수로 바꾼다.

이 구조 때문에 tokenizer, embedding, LM head는 한 묶음으로 봐야 한다.

tokenizer = 어떤 조각을 token으로 볼지
embedding = 그 token을 어떤 벡터에서 시작할지
LM head = 어떤 token 후보로 출력할지

그리고 이 묶음은 비용과도 연결된다.

vocabulary size
-> embedding / LM head 크기

tokenization 결과
-> sequence length
-> prefill workload
-> KV cache memory
-> decode context

이것이 "Tokenizer와 Embedding이 비용의 입구"라는 말의 의미다.

정리

token은 단어가 아니다. 모델의 vocabulary에 등록된 텍스트 조각이다. tokenizer는 문장을 token ID sequence로 바꾸고, embedding table은 그 ID를 벡터로 바꾼다. Transformer block stack을 지난 뒤에는 LM head가 hidden state를 vocabulary 전체의 logits로 바꾸고, 그중 하나의 token이 선택된다.

vocabulary가 커지거나 작아지는 것은 단순한 사전 크기 문제가 아니다. embedding table과 LM head의 크기, 같은 텍스트가 몇 token으로 쪼개지는지, sequence length와 KV cache 부담이 함께 움직인다.

그래서 LLM 비용을 이해하려면 price table만 보면 안 된다. 내 workload가 tokenizer에서 몇 token이 되는지, 그 token들이 prefill과 decode에서 어떤 sequence length를 만드는지까지 봐야 한다.

다음 글에서는 이 벡터들이 Transformer block 안에서 어떻게 바뀌는지 본다. self-attention, Q/K/V, causal mask, MLP/FFN, residual stream이 무엇을 하는지 정리한다.

이어 읽기

시리즈는 순서대로, 편집 추천은 맥락대로, 비슷한 주제는 태그 기준으로 정리합니다.

시리즈 전체

LLM 공부 시리즈2/9
  1. 1.LLM 공부 01 | LLM은 검색기가 아니라 다음 토큰 생성기다
  2. 2.LLM 공부 02 | 토큰이 비용을 만든다
  3. 3.LLM 공부 03 | Transformer 안에서 문맥이 섞이는 방식
  4. 4.LLM 공부 04 | Prefill과 Decode: LLM이 읽고 쓰는 두 단계
  5. 5.LLM 공부 05 | Batch와 Sequence Length가 속도와 비용을 정한다
  6. 6.LLM 공부 06 | MoE와 GPU 클러스터: 거대 모델은 어떻게 나뉘어 도는가
  7. 7.LLM 공부 07 | Reasoning Effort: 더 깊게 생각한다는 말의 실제 의미
  8. 8.LLM 공부 08 | 학습 Batch와 Distillation: 모델은 어떻게 바뀌는가
  9. 9.LLM 공부 09 | RAG와 Vector DB: LLM 밖에서 근거를 찾는 방식

비슷한 주제의 글

태그가 겹치는 글입니다. 시리즈와 편집 추천에 이미 나온 글은 제외합니다.