프롬프트 토큰 수 미리 세기 — 글자 수 어림과 실제 차이 실측 (tiktoken)

AI에 긴 문서를 넣기 전에 “이게 몇 토큰이지?”를 글자 수로 어림하는 경우가 많습니다. 흔히 듣는 기준이 “4글자에 1토큰”인데, 한글 글에서는 크게 빗나갑니다. 이 블로그에 실제로 발행한 한글 원고로 토큰 수 계산을 tiktoken으로 해 보니, 4글자=1토큰으로 어림한 값은 실제의 절반 정도(-42~-53%)였습니다. 영어 문서에서는 +16%로 꽤 맞았는데도요. 이 글에서는 tiktoken 설치와 처음 만난 SSL 오류, 글 종류별 실측표, 한글에 맞춘 어림식, 그리고 토큰 예산에 맞춰 자를 때 한글이 깨지는 문제까지 직접 돌린 결과로 정리합니다. 환경은 윈도우 11, Python 3.12.10, tiktoken 0.14.0입니다.

먼저 범위를 밝혀 둡니다. tiktoken은 OpenAI가 공개한 토크나이저라, 여기서 센 숫자는 OpenAI 모델 기준입니다. Claude처럼 다른 회사 모델은 토크나이저가 달라 숫자가 다르고, 정확한 값은 그 회사 API의 토큰 세기 기능으로 확인해야 합니다. 이 글에서는 그쪽은 재지 않았습니다. 다만 “한글은 글자 수 어림이 잘 안 맞는다”, “어림식은 실제 글로 맞춰 봐야 한다”는 방법 자체는 어느 모델에나 똑같이 쓸 수 있습니다.

tiktoken 설치하고 첫 줄 실행하기

설치는 pip install tiktoken 한 줄인데, 제 노트북에서는 여기서 바로 pip: Permission denied가 났습니다. 파이썬 설치 폴더의 pip.exe를 직접 실행하는 게 막힌 경우라, 파이썬을 통해 부르는 방식으로 바꾸니 바로 설치됐습니다.

python -m pip install tiktoken

사용법은 인코딩을 고르고 encode()로 토큰 목록을 받아 길이를 세는 게 전부입니다. o200k_base는 GPT-4o 계열, cl100k_base는 GPT-4·GPT-3.5 계열이 쓰는 인코딩입니다. 모델 이름을 알면 tiktoken.encoding_for_model()로 고를 수도 있습니다.

import tiktoken

enc = tiktoken.get_encoding("o200k_base")
ids = enc.encode("안녕하세요")
print(len(ids), ids)          # 2 [14307, 171731]

처음 만난 오류: CERTIFICATE_VERIFY_FAILED

그런데 첫 get_encoding()에서 오류가 났습니다. tiktoken은 인코딩 표 파일을 처음 한 번 인터넷에서 내려받는데, 그 다운로드가 인증서 검증에서 막혔습니다.

requests.exceptions.SSLError: HTTPSConnectionPool(host='openaipublic.blob.core.windows.net', port=443):
Max retries exceeded with url: /encodings/cl100k_base.tiktoken
(Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1010)')))

원인을 보려고 파이썬 기본 ssl 모듈로 같은 주소에 접속해 보니 이쪽은 성공했고, 받은 인증서의 발급자가 Norton Web/Mail Shield Root였습니다. 백신이 HTTPS 통신을 검사하려고 인증서를 자기 것으로 다시 서명하고 있었던 겁니다. 윈도우 인증서 저장소에는 이 백신 인증서가 등록돼 있지만, tiktoken이 쓰는 requests는 자체 인증서 묶음(certifi)만 믿기 때문에 거부한 것입니다.

verify=False처럼 검증을 끄는 대신, requests도 윈도우 인증서 저장소를 쓰게 하는 truststore로 해결했습니다. python -m pip install truststore 후 tiktoken보다 먼저 두 줄을 넣으면 됩니다.

import truststore
truststore.inject_into_ssl()   # tiktoken보다 먼저
import tiktoken

다운로드가 한 번 끝나면 파일이 캐시(%TEMP%\data-gym-cache)에 남아서 다음부터는 인터넷 없이 됩니다. 실제로 인코딩 로드 시간이 크게 달랐습니다.

# 첫 실행 (인코딩 파일 다운로드)
cl100k_base: 로드 3.876초, '안녕하세요' -> 5토큰 [31495, 230, 75265, 243, 92245]
o200k_base: 로드 5.121초, '안녕하세요' -> 2토큰 [14307, 171731]

# 두 번째 실행 (캐시 사용)
cl100k_base: 로드 0.119초, '안녕하세요' -> 5토큰 [31495, 230, 75265, 243, 92245]
o200k_base: 로드 0.253초, '안녕하세요' -> 2토큰 [14307, 171731]

여기서 벌써 차이가 보입니다. 같은 “안녕하세요”가 cl100k_base에서는 5토큰, o200k_base에서는 2토큰입니다. 옛 인코딩은 한글 한 글자를 여러 조각으로 나누는 경우가 많습니다.

글 종류별 실측: 4글자=1토큰은 영어에서만 맞았다

시험 글은 지어낸 문장 대신 실제 파일을 썼습니다. 이 블로그에 발행한 cProfile 글 본문, 그중 한글 비율이 높은 설명 문장만 골라낸 것, 파이썬 설치 폴더의 영어 LICENSE.txt, 표준 라이브러리 csv.py, 그리고 이 발행 자동화가 남긴 실행 로그입니다. 오차는 o200k_base 실제값 기준입니다.

$ python count_tokens.py
샘플                    글자   한글%  cl100k   o200k   글자/4    오차   글자/토큰
한글 설명문             4,951    50%   3,712   2,653   1,238   -53%     1.87
한글 글 전체(코드 포함) 10,941    23%   5,803   4,711   2,735   -42%     2.32
영어 문서(LICENSE)     36,235     0%   7,810   7,824   9,059   +16%     4.63
파이썬 코드(csv.py)    16,386     0%   3,743   3,760   4,096    +9%     4.36
실행 로그(한·영 혼합)   1,745    32%   1,187     931     436   -53%     1.87
속도 cl100k_base: 533,360자 -> 259,460토큰, 0.077초
속도 o200k_base: 533,360자 -> 213,100토큰, 0.070초
글 종류글자/토큰(o200k)“4글자=1토큰” 오차cl100k / o200k
한글 설명문1.87-53%3,712 / 2,653
한글 글 전체(코드 섞임)2.32-42%5,803 / 4,711
영어 문서4.63+16%7,810 / 7,824
파이썬 코드4.36+9%3,743 / 3,760
실행 로그(한·영 혼합)1.87-53%1,187 / 931

정리하면 세 가지입니다. 첫째, 영어와 코드는 4글자=1토큰이 그럭저럭 맞습니다. 둘째, 한글 설명문은 약 1.9글자에 1토큰이라, 4글자 기준으로 어림하면 실제의 절반으로 잡혀 예산을 넘기기 쉽습니다. 반대로 “한글은 1글자=1토큰”으로 잡으면 87% 크게 나옵니다. 셋째, 같은 한글 글이 cl100k_base에서는 o200k_base보다 약 1.4배 많고, 영어·코드는 두 인코딩 차이가 0.5% 이내입니다. 어느 모델에 보내는지에 따라 한글 비용이 달라진다는 뜻입니다.

속도는 걱정할 필요가 없었습니다. 한글 원고 두 편을 20번 이어 붙인 53만 자를 0.07초에 셌습니다.

한글에 맞춘 어림식 만들어 확인하기

매번 tiktoken을 돌리기 어려운 곳(예: 엑셀 수식, 다른 언어로 된 도구)에서 쓸 어림식이 있으면 편합니다. 한글 글자와 나머지 글자(영문·숫자·공백·기호)를 따로 세어 최소제곱으로 계수를 맞췄습니다. 식을 만들 때는 cProfile 원고, LICENSE, csv.py 세 개만 쓰고, 식을 만들 때 쓰지 않은 글로 맞는지 확인했습니다.

$ python estimate.py
o200k_base 어림식: 토큰 ≈ 한글 × 1.13 + 나머지 글자 × 0.218  (= 나머지 4.6글자당 1토큰)
확인용 글                  글자     실제   어림식    오차   글자/4    오차
다른 한글 글(10/21 원고)  15,727   5,944   5,747  -3.3%   3,932   -34%
실행 로그 10/22           1,745     931     887  -4.7%     436   -53%
실행 로그 10/23             247     118      67 -43.2%      62   -47%
SKILL.md                  9,035   4,719   5,513 +16.8%   2,259   -52%
def estimate_tokens(text):
    """o200k_base 기준 어림. 한도에 가까우면 실제로 세세요."""
    hangul = sum("\uac00" <= ch <= "\ud7a3" for ch in text)
    other = len(text) - hangul
    return round(hangul * 1.13 + other * 0.218)

다른 날 쓴 한글 원고는 -3.3%, 긴 실행 로그는 -4.7%로 꽤 맞았습니다. 하지만 247자짜리 짧은 로그는 -43%로 크게 빗나갔습니다. 열어 보니 날짜·시각·윈도우 경로·버전 번호가 대부분인 세 줄이었습니다. 숫자와 경로는 잘게 쪼개지는 편이라 글자 수로는 잘 안 잡힙니다. 마크다운 표와 명령어가 많은 SKILL.md는 반대로 +16.8%였습니다. 그래서 어림식은 “여유가 충분한지” 판단하는 데만 쓰고, 한도에 가까우면 실제로 세는 게 안전합니다.

토큰 예산에 맞춰 자를 때 한글이 깨지는 문제

긴 글을 “앞에서 N토큰까지만” 넣으려면 토큰 목록을 잘라 다시 글자로 되돌리면 됩니다. 그런데 cl100k_base로 해 보니 끝에 깨진 글자 �가 붙었습니다.

cl100k_base: 24토큰
  앞 3토큰 decode -> '자동화'
  앞 4토큰 decode -> '자동화 �'
  앞 5토큰 decode -> '자동화 스�'
o200k_base: 16토큰
  앞 3토큰 decode -> '자동화 스'
  앞 4토큰 decode -> '자동화 스크'
  앞 5토큰 decode -> '자동화 스크립'

cl100k_base는 한글 한 글자를 여러 토큰(바이트 조각)으로 나누기 때문에, 중간에서 자르면 반쪽짜리 글자가 남습니다. o200k_base는 이 문장에서는 깨지지 않았지만, 인코딩에 따라 생길 수 있는 문제라 처리해 두는 게 좋습니다.

처음에는 “글자로 되돌리다 오류가 나면 토큰을 하나씩 덜어내는” 방식으로 짰는데, 이러면 멀쩡한 글자까지 버렸습니다. 5토큰 예산이면 자동화 스까지 담을 수 있는데 자동화만 남았습니다. 바이트로 되돌린 뒤 끝의 반쪽 글자 바이트만 버리도록 고친 최종본입니다.

def cut_to_tokens(text, limit, enc):
    """limit 토큰 이하가 되도록 자르되, 끝에 걸친 반쪽 글자만 버립니다."""
    ids = enc.encode(text)
    if len(ids) <= limit:
        return text
    return enc.decode_bytes(ids[:limit]).decode("utf-8", errors="ignore")
limit 3: v1 '자동화' (3토큰) / v2 '자동화' (3토큰)
limit 4: v1 '자동화' (3토큰) / v2 '자동화 ' (4토큰)
limit 5: v1 '자동화' (3토큰) / v2 '자동화 스' (5토큰)
limit 8: v1 '자동화 스크' (7토큰) / v2 '자동화 스크' (7토큰)
limit 1~2000 시험: 예산 초과 0건

v1이 처음 버전, v2가 고친 버전입니다. 고친 함수를 15,727자 원고에 예산 1~2,000토큰으로 2,000번 돌려, 잘라낸 글을 다시 셌을 때 예산을 넘은 경우는 0건이었습니다.

파일 토큰 수를 세고 예산을 검사하는 스크립트

위 내용을 모아 바로 쓸 수 있는 명령줄 도구로 만들었습니다. 파일 여러 개를 주면 각각과 합계를 세고, 합계가 예산을 넘으면 얼마나 줄여야 하는지 출력한 뒤 종료 코드 1로 끝납니다. 다른 스크립트에서 AI를 부르기 전에 이걸 먼저 돌려 종료 코드로 걸러낼 수 있습니다.

"""파일의 토큰 수를 세고 예산을 넘는지 알려 줍니다.

python tokcount.py 파일1 [파일2 ...] [--budget 4000] [--encoding o200k_base]
"""
import argparse
import sys

try:
    import truststore
    truststore.inject_into_ssl()  # 백신이 SSL을 검사하는 PC에서 첫 다운로드 실패 방지
except ImportError:
    pass
import tiktoken

sys.stdout.reconfigure(encoding="utf-8")

parser = argparse.ArgumentParser(description="파일 토큰 수 세기")
parser.add_argument("files", nargs="+")
parser.add_argument("--budget", type=int, default=4000, help="파일 전체 합계 상한(토큰)")
parser.add_argument("--encoding", default="o200k_base")
args = parser.parse_args()

enc = tiktoken.get_encoding(args.encoding)
total = 0
for path in args.files:
    with open(path, encoding="utf-8") as f:
        text = f.read()
    n = len(enc.encode(text))
    total += n
    print(f"{n:>8,}토큰  {len(text):>8,}자  {path}")

print(f"{total:>8,}토큰  합계 ({args.encoding}, 예산 {args.budget:,})")
if total > args.budget:
    print(f"예산 초과: {total - args.budget:,}토큰을 줄여야 합니다.")
    sys.exit(1)
$ python tokcount.py ko_post.txt ko_post2.txt --budget 8000
   4,711토큰    10,941자  ko_post.txt
   5,944토큰    15,727자  ko_post2.txt
  10,655토큰  합계 (o200k_base, 예산 8,000)
예산 초과: 2,655토큰을 줄여야 합니다.
[exit 1, 0.54초]

원고 두 편을 합치면 10,655토큰이라 8,000 예산을 2,655토큰 넘는다고 알려 줍니다. 같은 원고 한 편을 --encoding cl100k_base로 세면 4,711이 아니라 5,803토큰이 나왔습니다. 보낼 모델에 맞는 인코딩을 고르는 게 중요합니다.

주의할 점

  • tiktoken 숫자는 OpenAI 모델 기준입니다. 다른 회사 모델은 토크나이저가 달라 숫자가 다릅니다. 정확한 값이 필요하면 그 모델 API의 토큰 세기 기능을 쓰세요.
  • 첫 실행에는 인터넷이 필요합니다. 인코딩 파일을 내려받기 때문입니다(이번에 3.9~5.1초). 회사망·백신 SSL 검사 환경에서 CERTIFICATE_VERIFY_FAILED가 나면 검증을 끄지 말고 truststore를 쓰세요.
  • 한글은 4글자=1토큰이 아닙니다. 이번 실측에서 한글 설명문은 약 1.9글자에 1토큰(o200k_base)이었습니다.
  • 숫자·경로·표가 많은 글은 어림이 크게 빗나갑니다. 짧은 로그에서 -43%, 마크다운 문서에서 +17%가 나왔습니다.
  • 토큰 목록을 잘라 decode하면 한글이 깨질 수 있습니다. decode_bytes() 후 errors="ignore"로 끝의 반쪽 글자만 버리세요.
  • 윈도우에서 pip가 Permission denied로 막히면 python -m pip로 실행해 보세요.

마무리

한글 프롬프트는 글자 수를 4로 나누지 말고, 실제로 세거나 한글을 따로 센 어림식을 쓰세요.

가장 작은 첫걸음은 이렇습니다.

  1. python -m pip install tiktoken으로 설치하고, 평소 AI에 붙여 넣는 글 하나를 len(enc.encode(text))로 세어 봅니다.
  2. 같은 글의 글자 수와 나눠 “내 글은 몇 글자에 1토큰인지” 확인합니다. 한글 위주라면 2 안팎이 나올 겁니다.
  3. AI를 부르는 스크립트 앞에 위 tokcount.py 같은 예산 검사를 넣어, 한도를 넘는 입력은 보내기 전에 걸러냅니다.

다음 글에서는 JSONDecodeError가 났을 때 깨진 JSON 파일에서 틀린 곳을 정확히 찾아내는 방법을 실측해 보겠습니다.

댓글 남기기