단어 토크나이저(word tokenizer)는 본 적 없는 단어 앞에서 막힙니다. 문자 토크나이저(character tokenizer)는 시퀀스 길이(sequence length)를 폭발시킵니다. 서브워드 토크나이저(subword tokenizer)는 그 중간을 잡습니다. 현대의 모든 LLM은 이 위에서 배포(ship)됩니다.
유형: Learn
언어: Python
선수 강의: Phase 5 · 01 (Text Processing), Phase 5 · 04 (GloVe / FastText / Subword)
예상 시간: 약 60분
학습 목표
바이트 페어 인코딩(Byte-Pair Encoding; BPE), 워드피스(WordPiece), 유니그램(Unigram) 토크나이제이션의 차이를 설명합니다.
센텐스피스(SentencePiece), tiktoken, Hugging Face Tokenizers 각각의 역할을 구분합니다.
문자 커버리지(character coverage), 바이트 폴백(byte fallback), 어휘 크기(vocabulary size)를 배포 요구에 맞게 선택합니다.
토크나이저 드리프트(tokenizer drift)와 다국어 학습 부족(multilingual undertraining) 같은 운영 단계의 함정을 식별합니다.
문제
어휘집(vocabulary)에 단어(word) 50,000개가 들어 있다고 가정합니다. 사용자가 untokenizable이라고 입력합니다. 토크나이저는 [UNK]를 반환합니다. 이제 모델은 그 단어에 대한 신호(signal)를 전혀 받지 못합니다. 더 나쁜 점도 있습니다. 코퍼스(corpus)의 90 백분위수(90th-percentile) 문서가 희귀 단어(rare word) 40개를 갖고 있다면, 문서마다 40 비트(bit)에 해당하는 정보가 그대로 사라지는 셈입니다.
서브워드 토큰화(subword tokenization)는 이 문제를 해결합니다. 자주 쓰이는 단어는 단일 토큰(single token)으로 그대로 남고, 희귀 단어는 의미 있는 조각으로 분해됩니다. 예를 들어 untokenizable은 un, token, izable로 쪼개질 수 있습니다. 어떤 문자열도 결국 바이트(byte) 시퀀스이므로, 학습 데이터(training data)는 모든 입력을 다룰 수 있습니다.
2026년 시점의 모든 프런티어(frontier) LLM은 세 가지 알고리즘(BPE, Unigram, WordPiece) 중 하나와, 세 가지 라이브러리(tiktoken, SentencePiece, HF Tokenizers) 중 하나를 사용합니다. 언어 모델(language model)을 배포하려면 반드시 그 중 하나의 토크나이저를 골라야 합니다.
사전 테스트
2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요
1.고정된 50k 어휘집(vocabulary)을 가진 단어 수준 토크나이저(word-level tokenizer)가 희귀 단어(rare word)에서 정보를 잃는 이유는 무엇인가요?
2.바이트 페어 인코딩(BPE)과 유니그램(Unigram) 토크나이제이션이 어휘집을 구축하는 방식의 차이는 무엇인가요?
0/2 답변 완료
개념
BPE(Byte-Pair Encoding). 문자 단위(character-level) 어휘집에서 시작합니다. 인접한(adjacent) 모든 쌍(pair)을 세고, 가장 자주 등장하는 쌍을 새로운 토큰으로 병합(merge)합니다. 목표 어휘 크기에 도달할 때까지 이 과정을 반복합니다. 현재 가장 지배적인 알고리즘이며, GPT-2/3/4, Llama, Gemma, Qwen2, Mistral이 모두 이 방식을 사용합니다.
바이트 단위 BPE(Byte-level BPE). 동일한 알고리즘을 유니코드(Unicode) 문자 대신 원시(raw) 바이트(256개 기본 토큰) 위에서 적용합니다. 어떤 바이트 시퀀스도 인코딩되므로 [UNK] 토큰이 0개임을 보장합니다. GPT-2는 토큰 50,257개를 사용합니다(256 bytes + 50,000 merges + 1 special).
유니그램(Unigram). 거대한 어휘집에서 시작해 각 토큰에 유니그램 확률(unigram probability)을 부여합니다. 그 다음, 제거했을 때 코퍼스의 로그 우도(log-likelihood)를 가장 덜 떨어뜨리는 토큰부터 반복적으로 가지치기(prune)합니다. 추론(inference) 단계에서 확률적(probabilistic)으로 동작하기 때문에 토크나이제이션을 샘플링할 수 있고, 이 성질은 서브워드 정규화(subword regularization)를 활용한 데이터 증강(data augmentation)에 유용합니다. T5, mBART, ALBERT, XLNet, Gemma가 이 방식을 사용합니다.
워드피스(WordPiece). 단순한 빈도 대신 학습 코퍼스의 우도(likelihood)를 최대로 키우는 쌍을 병합합니다. BERT, DistilBERT, ELECTRA가 사용합니다.
SentencePiece vs tiktoken. SentencePiece는 원시 유니코드 텍스트에서 어휘집(BPE 또는 Unigram)을 직접 *학습(train)*하는 라이브러리이며, 공백(whitespace)을 ▁ 기호로 인코딩합니다. tiktoken은 사전 구축된 어휘집에 대해 동작하는 OpenAI의 빠른 *인코더(encoder)*이며, 학습 기능은 제공하지 않습니다.
경험 법칙(rule of thumb)은 다음과 같습니다.
새 어휘집을 학습할 때: SentencePiece(다국어 지원, 사전 토크나이제이션 불필요) 또는 HF Tokenizers를 사용합니다.
GPT 어휘집에 대해 빠르게 추론할 때: tiktoken(cl100k_base, o200k_base)을 사용합니다.
학습과 서빙(serving) 모두 필요할 때: HF Tokenizers를 사용합니다. 하나의 라이브러리로 학습과 서빙을 모두 처리할 수 있습니다.
직접 만들기
Step 1: BPE 처음부터 구현하기
code/main.py를 참고합니다. 핵심 반복문은 다음과 같습니다.
deftrain_bpe(corpus, num_merges):
vocab = {tuple(word) + ("</w>",): count for word, count in corpus.items()}
merges = []
for _ inrange(num_merges):
pairs = Counter()
for symbols, freq in vocab.items():
for a, b inzip(symbols, symbols[1:]):
pairs[(a, b)] += freq
ifnot pairs:
break
best = pairs.most_common(1)[0][0]
merges.append(best)
vocab = apply_merge(vocab, best)
return merges
이 알고리즘이 표현하는 사실은 세 가지입니다. 첫째, </w>는 단어의 끝(word end)을 표시해 접미사(suffix) "low"와 접두사(prefix) "lower"가 구분되도록 합니다. 둘째, 빈도 가중(frequency weighting) 덕분에 빈도가 높은 쌍이 먼저 병합됩니다. 셋째, 병합 목록(merge list)은 순서가 있는 리스트이며, 추론은 학습 시점의 순서대로 병합을 적용합니다.
Step 2: 학습된 병합으로 인코딩하기
defencode_bpe(word, merges):
symbols = list(word) + ["</w>"]
for a, b in merges:
i = 0while i < len(symbols) - 1:
if symbols[i] == a and symbols[i + 1] == b:
symbols = symbols[:i] + [a + b] + symbols[i + 2:]
else:
i += 1return symbols
순진하게(naive) 구현하면 시간 복잡도는 O(n·|merges|)입니다. 실제 운영 구현(tiktoken, HF Tokenizers)은 병합 순위 조회(merge-rank lookup)와 우선순위 큐(priority queue)를 사용해 사실상 선형 시간에 가깝게 동작합니다.
Step 3: 실무에서의 SentencePiece
import sentencepiece as spm
spm.SentencePieceTrainer.train(
input="corpus.txt",
model_prefix="my_tokenizer",
vocab_size=8000,
model_type="bpe", # 또는 "unigram"
character_coverage=0.9995, # CJK는 더 낮게 (영어 0.9995, 일본어 0.995)
normalization_rule_name="nmt_nfkc",
)
sp = spm.SentencePieceProcessor(model_file="my_tokenizer.model")
print(sp.encode("untokenizable", out_type=str))
# ['▁un', 'token', 'izable']
주의할 점은 세 가지입니다. 별도의 사전 토크나이제이션(pre-tokenization)이 필요 없습니다. 공백은 ▁ 기호로 인코딩됩니다. character_coverage는 희귀 문자를 얼마나 적극적으로 보존할지, 아니면 <unk>로 대응시킬지를 결정합니다.
인코딩 전용(encoding-only)입니다. Rust 백엔드 덕에 매우 빠릅니다. GPT-4/5의 토크나이제이션과 정확히 일치하므로, 바이트 카운팅(byte counting), 비용 추정(cost estimation), 컨텍스트 윈도(context window) 예산 산정에 그대로 활용할 수 있습니다.
2026년에도 여전히 마주치는 함정(pitfall)
토크나이저 드리프트(tokenizer drift). 학습은 어휘집 A로 했는데 배포는 어휘집 B로 한 경우입니다. 토큰 ID가 달라지면서 모델의 출력이 망가집니다. CI에서 tokenizer.json 해시(hash)를 검증합니다.
공백 모호성(whitespace ambiguity). BPE에서 "hello"와 " hello"는 서로 다른 토큰을 만듭니다. 항상 add_special_tokens와 add_prefix_space를 명시적으로 지정합니다.
다국어 학습 부족(multilingual undertraining). 영어 비중이 큰 코퍼스로 학습하면 비라틴(non-Latin) 문자를 510배 더 많은 토큰으로 쪼개는 어휘집이 나옵니다. GPT-3.5에서는 같은 프롬프트가 일본어/아랍어로 입력될 때 510배 더 비싸지기도 합니다. o200k_base가 이 문제를 일부 개선했습니다.
이모지 분할(emoji split). 이모지 하나가 토큰 5개를 차지할 수 있습니다. 컨텍스트 예산을 잡을 때 이모지 처리 방식을 반드시 체크해 둡니다.
사용하기
2026년의 토크나이저 스택은 다음과 같습니다.
상황
선택
단일 언어(monolingual) 모델을 처음부터 학습
HF Tokenizers(BPE)
다국어(multilingual) 모델 학습
SentencePiece(Unigram, character_coverage=0.9995)
OpenAI 호환 API 서빙
tiktoken(o200k_base, GPT-4 이상용)
도메인 특화 어휘집(코드·수학·단백질 등)
도메인 코퍼스로 커스텀 BPE를 학습한 뒤 베이스 어휘집과 병합
엣지(edge) 추론, 소형 모델
Unigram(작은 어휘집에서 더 잘 동작함)
어휘 크기는 고정 상수가 아니라 스케일링(scaling) 결정 사항입니다. 대략의 경험 법칙은 다음과 같습니다. 파라미터 10억(1B) 미만에는 32k, 10억100억(110B)에는 50~100k, 다국어/프런티어 모델에는 200k 이상.
산출물 만들기
outputs/skill-tokenizer-picker.md로 저장합니다.
---
name: tokenizer-picker
description: Pick tokenizer algorithm, vocab size, library for a given corpus and deployment target.
version: 1.0.0
phase: 5
lesson: 19
tags: [nlp, tokenization]
---
Given a corpus (size, languages, domain) and deployment target (training from scratch / fine-tuning / API-compatible inference), output:
Guide the student in Korean.
1. Algorithm. BPE, Unigram, or WordPiece. One-sentence reason.
2. Library. SentencePiece, HF Tokenizers, or tiktoken. Reason.
3. Vocab size. Rounded to nearest 1k. Reason tied to model size and language coverage.
4. Coverage settings. `character_coverage`, `byte_fallback`, special-token list.
5. Validation plan. Average tokens-per-word on held-out set, OOV rate, compression ratio, round-trip decode equality.
Refuse to train a character-coverage <0.995 tokenizer on corpora with rare-script content. Refuse to ship a vocab without a frozen `tokenizer.json` hash check in CI. Flag any monolingual tokenizer under 16k vocab as likely under-spec.
이 스킬은 코퍼스(크기, 언어, 도메인)와 배포 대상(처음부터 학습 / 파인튜닝 / API 호환 추론)을 입력으로 받아, 알고리즘·라이브러리·어휘 크기·커버리지 설정·검증 계획을 함께 제시하도록 안내합니다. 또한 희귀 문자(rare-script)가 포함된 코퍼스에서 문자 커버리지가 낮은 토크나이저를 학습하지 못하게 하고, CI에서 tokenizer.json 해시를 고정해 검증하지 않은 어휘집을 배포하지 못하게 막습니다.
연습문제
쉬움.code/main.py의 작은 코퍼스에 대해 500회 병합 BPE를 학습합니다. 보류 평가 단어(held-out word) 3개를 인코딩한 뒤, 정확히 1 토큰이 된 단어와 1 토큰보다 많이 분할된 단어가 각각 몇 개인지 확인합니다.
중간. 영어 위키피디아 문장 100개에 대해 cl100k_base, o200k_base, 그리고 직접 vocab=32k로 학습한 SentencePiece BPE의 토큰 수를 비교합니다. 각각의 압축비(compression ratio)를 보고합니다.
어려움. 같은 코퍼스에 BPE, Unigram, WordPiece를 각각 학습합니다. 작은 감성 분류기(sentiment classifier)에 각 토크나이저를 적용했을 때의 다운스트림(downstream) 정확도를 측정합니다. 토크나이저 선택이 F1 점수를 1점 이상 움직이는지 확인합니다.
핵심 용어
용어
흔한 설명
실제 의미
BPE(Byte-Pair Encoding)
바이트 쌍 인코딩
목표 어휘 크기에 도달할 때까지 가장 자주 등장하는 문자 쌍을 탐욕적(greedy)으로 병합한다.
바이트 단위 BPE(Byte-level BPE)
미지 토큰이 절대 없음
원시 256바이트 위에서 동작하는 BPE이며, GPT-2와 Llama가 이를 사용한다.
Unigram
확률적 토크나이저
큰 후보 집합에서 로그 우도를 기준으로 가지치기한다. T5, Gemma가 사용한다.
SentencePiece
공백을 다루는 그것
원시 텍스트에서 BPE/Unigram을 학습하는 라이브러리이며, 공백을 ▁로 인코딩한다.
tiktoken
빠른 그것
사전 구축 어휘집을 위한 OpenAI의 Rust 기반 BPE 인코더이며, 학습 기능은 없다.