토크나이저 — AI는 왜 글자를 못 세나

토큰은 토크나이저가 자주 붙어 다니는 글자 묶음을 사전에 미리 등록해 두고, 문장을 그 사전 항목 단위로 자른 조각이다. 문장 속 strawberry는 이 사전에서 토큰 한 개다. 모델은 그 한 개 안에 r이 몇 개 들었는지 따로 배우지 않으면 알지 못한다.

strawberry 소동

사람은 글자를 하나씩 보고, AI는 단어 하나를 통짜 토큰으로 받는다는 좌우 비교

2024년 여름, 챗봇에 "strawberry에 r이 몇 개야?"라고 물은 스크린샷이 인터넷을 돌았다. 돌아온 답은 2였다. 세 살 아이도 손가락으로 짚어 가며 세는 것을, 방대한 텍스트를 읽은 모델이 왜 틀렸을까.

계산을 못 해서는 아니다. 같은 모델이 정규식을 고쳐 주고, 긴 논문을 요약하고, 코드의 버그도 짚어 낸다. 그런데 단어 하나에 든 글자를 세는 일에서는 자주 미끄러졌다. 한 블로거가 ChatGPT에 같은 질문을 던져 2라는 답을 받은 과정을 기록해 두었다. 여러 모델이 똑같이 2라고 답했다는 보고가 토론 게시판에도 모였다.

원인 하나는 모델이 글을 받아들이는 방식에 있다. 문장 속 단어를 받을 때 모델은 글자 단위로 받지 않는다. 모델에게 들어가는 최소 단위는 글자가 아니라 토큰이고, 문장 속 strawberry는 그 단위로 딱 하나다. 그 한 개가 어떻게 만들어지고, 그 한 개가 글자 세기 말고 무엇을 더 설명하는지를 차례로 확인한다.

토큰이라는 최소 단위

토크나이저는 글을 자르는 도구다. 자주 붙어 다니는 글자 묶음을 미리 사전에 올려 두고, 문장이 들어오면 그 사전에 있는 항목 단위로 자른다. 잘린 조각 하나가 토큰이고, 모델이 실제로 받는 것은 글자가 아니라 조각마다 붙은 번호다. 사전이 다르면 자르는 자리도 달라진다. 그래서 토큰은 언어의 성질이 아니라 그 사전의 성질이다.

직접 잘라 보면 금방 보인다. OpenAI가 공개한 tiktoken 라이브러리로 GPT-4o 계열이 쓰는 사전(o200k_base)을 불러 " strawberry"를 넣어 본다. 앞에 공백을 붙인 이유는 문장 안에서는 단어 앞에 공백이 붙어 들어오기 때문이다.

import tiktoken

enc = tiktoken.get_encoding("o200k_base")   # GPT-4o 계열이 쓰는 사전

ids = enc.encode(" strawberry")             # 문장 안에 있을 때의 형태(앞 공백 포함)
print(len(ids), ids)
print([enc.decode([i]) for i in ids])
print([enc.decode([i]) for i in enc.encode("strawberry")])   # 단독으로 썼을 때
1 [101830]
[' strawberry']
['st', 'raw', 'berry']

여기서 답이 나온다. 문장 속 strawberry는 토큰 한 개이고, GPT-4/3.5 계열 사전(cl100k_base)에서도 마찬가지로 한 개다. 모델이 그 자리에서 받는 것은 101830이라는 번호 하나다. 그 번호만 봐서는 안에 r이 몇 개 들었는지 알 수 없다. 사람이 "삼십칠"이라는 단어를 듣고 획이 몇 개인지 바로 말하지 못하는 것과 비슷하다. 세려면 번호를 다시 글자로 풀어야 하는데, 모델은 그 풀이를 학습 중에 따로 외웠을 때만 할 수 있다.

같은 단어를 단독으로 쓰면 st·raw·berry 세 조각으로 갈린다. 잘린 자리를 보면 raw와 berry가 나오는데, 이 경계는 영어의 음절 구분과 반드시 맞지는 않는다. 그저 그 글자 묶음이 학습 말뭉치에서 자주 붙어 다녔을 뿐이다.

그림 1는 단어 열넷을 같은 사전으로 잘라 조각마다 색을 입힌 그림이다. blueberry는 blue와 berry 둘로 깔끔하게 갈리지만 hippopotamus는 넷으로 부서지고, 한글 단어는 글자 수와 조각 수가 맞지 않는다. 조각의 크기도, 경계의 자리도 우리가 글자를 세는 방식과 어긋나 있다. 이렇게 어긋나게 자르는 데는 이유가 있다.

영단어 여덟 개와 한글 단어 여섯 개를 o200k 사전으로 자른 토큰 경계 띠
그림 1. 단어를 단독으로 넣어 같은 사전으로 자른 단어 열넷. 색이 바뀌는 자리가 토큰 경계이고, 배지 숫자는 그 단어에 든 r의 개수다. 색은 프레임 안에서 조각을 구분하기 위한 것이라 단어끼리 비교하는 값이 아니다.

왜 이렇게 자르나

글자 하나를 그대로 토큰 하나로 삼으면 사전은 아주 단순해진다. 대신 문장이 길어진다. 자주 나오는 덩어리를 통째로 한 토큰에 담으면 같은 문장이 훨씬 짧은 번호 열로 줄어든다. 처리 비용도 열 길이를 따라 늘어난다. 사전을 잘 만드는 일이 곧 비용을 아끼는 일이다. 토크나이저를 바닥부터 만들어 보는 Karpathy의 강의가 이 과정을 그대로 보여준다.

이 사전을 만드는 대표적인 방법이 바이트 쌍 인코딩(Byte Pair Encoding, BPE)이다. 방식은 한 줄로 끝난다. 말뭉치에서 가장 자주 붙어 나오는 두 조각을 찾아 하나로 합치고, 그 합치기를 정해진 횟수만큼 반복해 사전을 채운다. berry가 한 덩어리로 남은 것도, strawberry가 문장 속에서 한 토큰이 된 것도 그 반복의 결과다.

효율은 실제로 나온다. 아래에서 쓸 자작 문장 열두 쌍에서 영어는 대략 4.3자마다 토큰 하나였다. OpenAI 안내도 영어 1토큰을 대략 네 글자로 잡는다. 문제는 이 사전이 어떤 언어의 통계로 만들어졌느냐다.

한국어가 치르는 값

같은 뜻의 문장을 한국어와 영어로 나란히 열두 쌍 만들어 토큰 수를 세어 봤다. 합계 기준으로 한국어는 영어의 1.37배(o200k_base), 2.08배(cl100k_base)를 썼다. 표본은 열두 쌍짜리 자작 문장이다. 정확한 상수가 아니라 대략의 크기로 읽어야 한다. 그래도 방향은 뚜렷하다. 신형 사전이 한국어 토큰 수를 3분의 1가량 줄였고(229→152), 여전히 한국어가 더 많이 쓴다.

같은 뜻 문장 열두 쌍의 영어 토큰 수(회색 점)와 한국어 토큰 수(색 점)를 한 줄로 이은 비교, 사전 두 종류 두 패널
그림 2. 같은 뜻 문장 열두 쌍. 한 줄이 문장 한 쌍이다. 회색 점이 영어, 색 점이 한국어 토큰 수이고, 두 패널은 사전 두 종류(왼쪽 o200k_base·오른쪽 cl100k_base)다. 열두 쌍 모두에서 한국어 점이 오른쪽에 있다.

그림 2에서 두 점 사이 거리가 그대로 비용이다. 토큰이 많이 든다는 말은 같은 내용을 넣는 데 돈이 더 든다는 뜻이고, 컨텍스트 한도도 그만큼 빨리 찬다는 뜻이다. 한 쌍만 직접 재어 보면 이렇다.

import tiktoken

ko = "인공지능은 글자를 세는 데 서툴다."
en = "AI is clumsy at counting letters."

for name in ["o200k_base", "cl100k_base"]:
    enc = tiktoken.get_encoding(name)
    print(name, "한국어", len(enc.encode(ko)), "/ 영어", len(enc.encode(en)))

enc = tiktoken.get_encoding("o200k_base")
print("딸기 조각:", [enc.decode_single_token_bytes(i) for i in enc.encode("딸기")])
o200k_base 한국어 14 / 영어 8
cl100k_base 한국어 20 / 영어 7
딸기 조각: [b'\xeb\x94', b'\xb8', b'\xea\xb8\xb0']

마지막 줄이 한국어가 값을 치르는 자리를 그대로 보여준다. 두 글자짜리 딸기가 세 조각으로 나뉘는데, 앞의 두 조각을 합쳐야 비로소 "딸" 한 글자가 된다.

한국어에서는 모델이 보는 최소 단위가 글자보다 작아지는 일이 흔하다. 영어에서는 단어 하나가 통으로 묶여 글자가 안 보이고, 한국어에서는 글자가 쪼개져서 안 보인다. 방향은 반대인데 결과는 같다.

내일 써먹는 체크 3줄

토큰 하나를 이해하면 세 가지가 같이 설명된다. 글자 세기·거꾸로 쓰기·끝말잇기처럼 글자 단위를 다루는 요청에 모델이 유독 약하다. 같은 말을 해도 한국어 프롬프트가 값을 더 치르고, 대화도 한도에 더 빨리 닿는다. 그리고 "한 글자씩 띄어서 써 봐"라는 요청이 통한다. strawberry를 s-t-r-a-w-b-e-r-r-y로 풀어 쓰면 두 사전 모두에서 글자마다 토큰이 하나씩 붙어 열 개가 된다. 통짜 번호표가 열 개의 낱개 번호표로 바뀌니 그제야 셀 것이 생긴다.

2026년 8월 18일, 같은 API의 모델 둘에 이 질문을 던졌다. 단어 여섯 개를 세 번씩 물었더니 gemini-3.5-flash-lite는 열여덟 번을 모두 맞혔다. gemma-4-26b-a4b-it는 답 대신 글자를 하나씩 풀어 쓰며 셌다(응답을 받은 두 번 모두). 뒤쪽 모델이 한 일이 방금 말한 우회법 그 자체다. 다만 이 기록은 단어 여섯 개짜리 표본이라 "이 모델은 글자를 셀 줄 안다"는 결론의 근거로는 쓸 수 없다.

토큰화가 글자 세기 실패의 유일한 원인은 아니다. 모델은 어떤 단어의 철자를 학습 데이터에서 통째로 외워 둘 수도 있고, 방금 본 것처럼 풀어 쓰며 세는 방식으로 우회할 수도 있다. 토큰은 왜 이 과제가 유독 어려운지를 설명하는 조건이지, 결과를 혼자 결정하는 원인이 아니다.

  • 글자·철자를 센 답은 그대로 쓰지 말고 한 번 검산한다.
  • 한국어로 긴 문서를 넣을 때는 한도에 여유를 두고 잡는다.
  • 글자 단위가 중요한 질문은 s-t-r-a-w-b-e-r-r-y처럼 풀어 써서 물어본다.

더 읽기

  • OpenAI Help, "What are tokens and how to count them?" — https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
  • openai/tiktoken (모델→사전 매핑은 encoding_for_model) — https://github.com/openai/tiktoken
  • Andrej Karpathy, "Let’s build the GPT Tokenizer" (2024) — https://www.youtube.com/watch?v=zduSFxRajkE