LLM 공부 02. 토큰이 비용을 만든다
이 글의 핵심은 token이 모델의 입력 단위이면서 사용자가 체감하는 비용 단위라는 점입니다.
1편에서는 LLM을 검색기가 아니라 다음 token 생성기로 다시 잡았습니다.
이번 글에서는 그 첫 단계로 내려갑니다. 사용자가 쓴 문장은 곧바로 Transformer block으로 들어가지 않습니다. 먼저 tokenizer를 지나 token ID가 되고, token ID는 embedding table을 지나 벡터가 됩니다. 그리고 모델이 답을 만들 때는 다시 LM head를 통해 vocabulary 전체의 token 후보로 돌아옵니다.
이 경로가 중요한 이유는 단순합니다. token 수가 sequence length, KV cache, prefill workload, 사용자 비용으로 이어지기 때문입니다.
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는 일련번호입니다. 15339가 1917보다 의미상 더 크거나 더 중요하다는 뜻이 아닙니다. 모델이 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 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 비용은 입구부터 출구까지 이어집니다
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.LLM 공부 01. LLM은 검색기가 아니라 다음 토큰 생성기다
- 2.LLM 공부 02. 토큰이 비용을 만든다
- 3.LLM 공부 03. Transformer 안에서 문맥이 섞이는 방식
- 4.LLM 공부 04. Prefill과 Decode는 LLM이 읽고 쓰는 두 단계다
- 5.LLM 공부 05. Batch와 Sequence Length가 속도와 비용을 정한다
- 6.LLM 공부 06. MoE와 GPU 클러스터는 거대 모델을 나누어 실행한다
- 7.LLM 공부 07. Reasoning Effort가 의미하는 추론 예산
- 8.LLM 공부 08. 학습 Batch와 Distillation은 모델을 어떻게 바꾸는가
- 9.LLM 공부 09. RAG와 Vector DB는 LLM 밖에서 근거를 찾는다
비슷한 주제의 글
태그가 겹치는 글입니다. 시리즈와 편집 추천에 이미 나온 글은 제외합니다.
AI 웹개발 기초 프론트엔드 1.1. 프론트엔드는 왜 이렇게 복잡해졌을까
프론트엔드가 React나 Next.js 같은 기술명 묶음이 아니라, 웹 문서가 구조, 표현, 동작, 상태, API 통신, 성능, 배포 책임을 차례로 떠안으며 커진 문제 해결의 축적임을 설명하는 AI 웹개발 기초 시리즈 프론트엔드 1-1.
AI 웹개발 기초 프론트엔드 1.2. DOM은 화면이 아니라 브라우저의 작업 모델이다
HTML source가 곧 화면이 아니라 DOM, CSSOM, render tree, layout, paint, composite, accessibility tree를 거쳐 화면과 보조기술 의미로 바뀌며, DOM 조작이 reflow, XSS, event delegation, 성능 지표와 어떻게 연결되는지 설명하는 AI 웹개발 기초 시리즈 프론트엔드 1-2.
AI 웹개발 기초 프론트엔드 1.3. jQuery에서 React로 넘어간 진짜 이유
jQuery의 DOM 직접 조작과 AJAX가 해결한 문제를 인정하고, UI가 커지면서 React, Vue, Angular 같은 state/data 기반 component UI가 왜 필요해졌는지 설명하는 AI 웹개발 기초 시리즈 프론트엔드 1-3.