NOTES / 02 / 2026.10.20

글자가 숫자가 되기까지.

지난 편

1편에서 LLM은 다음 토큰 하나를 고르는 일을 끝 신호가 나올 때까지 반복한다는 것을 봤다.

한 줄 결론

문장은 토큰으로 쪼개지고, 토큰마다 번호가 붙고, 각 번호는 정해진 숫자 목록(벡터)으로 바뀐다.

이 숫자 목록은 1편에서 숫자 묶음이라고 부른 것과 같다.

쉽게 보기

앱 서버는 시스템 지침, 이전 대화, 이번 질문을 요청 하나에 묶어 보낸다. Claude Messages API에서는 시스템 지침이 system에, 대화가 messages에 들어간다. 아래 요청은 이전 대화 없이 질문 하나만 보낸다.

{
  "model": "claude-opus-5-5",
  "max_tokens": 1024,
  "system": "짧게 답하세요.",
  "messages": [
    { "role": "user", "content": "토마토는 과일이야?" }
  ]
}

이전 대화가 있으면 messages에 user와 assistant 메시지가 번갈아 쌓이고, 이번 질문은 맨 끝에 들어간다. LLM 서버는 이 요청을 받아 네 단계를 거쳐 숫자로 바꾼다.

[요청] system: "짧게 답하세요."  messages: "토마토는 과일이야?"
    |
    | 1. 정해진 형식에 맞춰 텍스트 하나로 잇는다
    v
<system>짧게 답하세요.</system><user>토마토는 과일이야?</user><assistant>
    |
    | 2. 토크나이저가 토큰으로 자른다 (16개)
    v
<system> | 짧 | 게 | 답 | 하 | 세요 | . | </system> | <user> | ... | <assistant>
    |
    | 3. 토큰마다 토큰 ID로 바꾼다
    v
5, 21873, 1240, 6150, 703, 2981, 13, 6, 7, ..., 9
    |
    | 4. 토큰 ID마다 임베딩 표에서 숫자 목록을 꺼낸다
    v
숫자 목록 16개

1. 텍스트 하나로 잇기. 모델은 JSON 필드를 따로 받지 않고, 토큰이 한 줄로 늘어선 입력 하나만 받는다. 그래서 텍스트로 이을 때 <system>, <user> 같은 역할 표시 토큰을 끼워 넣어 누가 한 말인지 나타낸다. 맨 끝의 <assistant>는 여기서부터 답이 시작된다는 뜻이고, 모델은 이 뒤에 올 토큰을 고른다. 위 형식은 공개 모델(open-weight)에서 흔히 쓰는 모양을 본뜬 것이다. Claude의 실제 형식은 공개되어 있지 않다. 표시 토큰의 이름도 모델마다 다르다. 이 글의 <system>, <user>, <assistant>는 설명용으로 지은 이름이다.

2. 토큰으로 자르기. 토크나이저는 미리 정해 둔 토큰 목록에 있는 조각으로 텍스트를 자른다. 자주 나오는 조각은 길어도 한 토큰이 되고, 드문 조각은 글자 하나나 그보다 작은 단위로 잘린다. 예를 들어 뒤에 나오는 표에서 토마토는은 한 토큰이고 짧게는 두 토큰이다.

3. 토큰 ID로 바꾸기. 토큰 목록의 항목마다 고유 번호인 토큰 ID가 있다. 같은 토큰은 어느 요청에서든 같은 번호가 된다. 모델이 실제로 받는 입력은 이 번호의 나열이다.

아래는 이 연재가 끝까지 쓰는 예시 토큰 표다. 토큰 경계와 ID는 모두 가상 값이고, 띄어쓰기는 표에서 생략했다.

위치토큰토큰 ID구분
0<system>5역할 표시
1짧21873시스템 지침
2게1240시스템 지침
3답6150시스템 지침
4하703시스템 지침
5세요2981시스템 지침
6.13시스템 지침
7</system>6역할 표시
8<user>7역할 표시
9토마토는7342질문
10과일9180질문
11이412질문
12야1785질문
13?30질문
14</user>8역할 표시
15<assistant>9역할 표시(응답 시작)

모델이 이어서 고르는 출력 토큰은 [4512, 27, 9311, 4] 네 개다(가상 값).

위치토큰토큰 ID
16네4512
17,27
18과일입니다.9311
19<EOS>4

<EOS>가 1편의 끝 신호다. 이 토큰이 나오면 반복이 멈추므로 <EOS>는 다시 입력에 붙지 않는다.

4. 숫자 목록 꺼내기. 토큰 ID는 번호일 뿐이라서 7342와 7343이 뜻도 가깝다는 보장이 없다. 그래서 모델에는 토큰 ID마다 숫자 목록 하나를 저장한 표가 있고, 계산은 번호 대신 이 숫자 목록으로 한다. 이 표가 임베딩이다. 토마토는이 들어오면 7342번 줄을 그대로 꺼낸다.

7342 -> [0.12, -0.53, 0.88, ..., 0.07]   (가상 값)

표의 숫자는 학습하면서 정해지고, 비슷한 문맥에 자주 쓰이는 토큰끼리는 숫자 목록도 대체로 비슷해진다. 목록 길이는 모델마다 고정돼 있다. 공개 모델 Qwen2.5-7B는 토큰 하나를 숫자 3,584개로 나타낸다(config.json의 hidden_size). 이 길이라면 예시의 16개 토큰은 숫자 3,584개짜리 목록 16개가 된다.

새 용어

  • 토크나이저: 텍스트를 토큰으로 자르고 토큰 ID로 바꾸는 프로그램. 모델마다 자기 토크나이저가 있어서 같은 문장도 모델에 따라 토큰 수가 다르다.
  • 토큰 ID: 토크나이저의 토큰 목록에서 토큰마다 붙은 고유 번호. 모델이 받는 입력은 이 번호의 나열이다.
  • 임베딩: 토큰 ID마다 숫자 목록(벡터) 하나를 저장해 둔 표. 그 표에서 꺼낸 숫자 목록을 가리킬 때도 쓴다. 모델의 계산은 이 숫자 목록에서 시작한다.
더 깊이

API는 앞 대화를 기억하지 않는다.

Claude 문서는 Messages API가 stateless라서 요청마다 대화 전체를 보내야 한다고 설명한다. 모델이 다음 토큰을 고를 때 보는 것은 이번 요청의 입력 토큰뿐이다. 지난 요청의 대화를 따로 보관했다가 꺼내 보는 단계가 없다. 그래서 대화를 이어 가려면 앱 서버가 앞 대화를 다시 messages에 담는다. 위 질문에 이어 두 번째 질문을 보내면 요청은 이렇게 된다.

{
  "model": "claude-opus-5-5",
  "max_tokens": 1024,
  "system": "짧게 답하세요.",
  "messages": [
    { "role": "user", "content": "토마토는 과일이야?" },
    { "role": "assistant", "content": "네, 과일입니다." },
    { "role": "user", "content": "그럼 수박은?" }
  ]
}

대화가 길어질수록 요청 하나의 입력 토큰이 늘어난다. 캐싱을 켜지 않았다면 턴마다 응답의 usage.input_tokens가 커지는 것으로 확인할 수 있다. 요청마다 되풀이되는 같은 앞부분을 처음부터 다시 계산하지 않게 하는 방법은 7편에서 다룬다.

대화 형식 템플릿과 특수 토큰.

1번 단계에서 요청을 텍스트 하나로 잇는 규칙을 대화 형식 템플릿이라고 하고, 앞에서 본 역할 표시 토큰을 특수 토큰이라고 한다. 모델은 학습할 때 이 형식으로 된 대화를 아주 많이 본다. 그래서 <assistant> 뒤에는 답이 오고, 답이 끝나면 <EOS>가 온다는 흐름을 익힌다. 학습 때와 다른 형식으로 입력을 넣으면 답의 품질이 떨어질 수 있다.

특수 토큰은 보통 텍스트 조각과 별도로 토큰 목록에 예약된 항목이다. 공개 모델에서는 서빙 엔진이 템플릿을 적용하면서 메시지의 역할을 특수 토큰으로 바꿔 넣는다. 모델은 이 특수 토큰을 보고 시스템 지침과 질문을 구분한다. Claude API에서도 사용자는 특수 토큰을 직접 쓰지 않고 system과 role로 역할을 나눈다. 다만 system과 role이 내부에서 어떤 형식으로 바뀌는지는 공개되어 있지 않다.

한국어가 영어보다 토큰을 많이 쓰는 이유.

토크나이저의 토큰 목록은 학습용 텍스트에서 자주 나오는 조각을 모아 만든다. 학습용 텍스트에 영어가 많으면 영어 단어는 대부분 한 토큰이 되고, 한국어는 음절 하나 단위로 잘리는 일이 잦다. 목록에 없는 음절은 더 잘게 잘린다. 바이트 단위까지 내려가는 토크나이저라면 UTF-8로 3바이트인 한글 음절 하나가 토큰 여러 개가 될 수도 있다. 조사와 어미가 붙는 한국어 특성도 영향을 준다. 토마토는, 토마토가, 토마토를처럼 조사가 붙은 형태마다 학습용 텍스트에 자주 나와야 각각 한 토큰이 된다.

토큰이 많으면 요금이 늘고, 답이 나오기까지 계산할 양도 늘어난다. 요청을 보내기 전에 토큰 수를 알고 싶으면 토큰 세기 API(POST /v1/messages/count_tokens)에 같은 system, messages를 보내면 된다. 같은 문서에 따르면 Claude Opus 4.7부터 쓰는 새 토크나이저는 같은 텍스트를 이전 모델보다 약 30% 많은 토큰으로 자른다. 토큰 수는 모델을 바꿀 때마다 다시 세야 한다.

Notes 피드로 가기