Токенизация в LLM¶
~7 минут чтения
Предварительно: Позиционное кодирование | Реализация внимания с нуля
Зачем токенизация¶
Токенизация -- мост между текстом и числами. LLM не видит буквы: она оперирует токенами -- подсловами, которые определяют что модель "понимает" и сколько стоит обработка.
Ключевой инсайт: выбор токенизатора определяет не только качество модели, но и её экономику. Byte-Level BPE (GPT-4, LLaMA 3) стал стандартом: нулевой OOV, универсальное покрытие языков и кода, предсказуемое поведение.
Почему токенизация критична¶
| Аспект | Влияние |
|---|---|
| Стоимость API | Больше токенов = выше цена |
| Compute | Длина последовательности -> \(O(n^2)\) attention |
| Мультиязычность | Плохой tokenizer = 2-5x больше токенов |
| Код | Неправильное разбиение -> баги |
| Возможности модели | Токены определяют что модель "видит" |
Алгоритмы токенизации¶
Byte-Pair Encoding (BPE)¶
Used by: GPT-2, GPT-3, GPT-4, Llama, Mistral
Algorithm¶
- Start with character-level vocabulary
- Count frequency of adjacent pairs
- Merge most frequent pair into new token
- Repeat until target vocab size
Формула BPE¶
На каждой итерации мерджится пара с максимальным score.
Unigram Language Model¶
Used by: T5, ALBERT, some multilingual models
Algorithm¶
- Start with large vocabulary
- Compute probability of each token
- Remove tokens with lowest impact
- Use Viterbi for optimal segmentation
Вероятность Unigram¶
Где \(p(t)\) оценивается EM-алгоритмом.
WordPiece¶
Used by: BERT, ELECTRA
Аналогичен BPE, но использует scoring на основе правдоподобия:
Byte-Level BPE¶
Used by: GPT-2+, modern LLMs
Ключевая идея: работать с UTF-8 байтами, а не символами
- Нулевой OOV (out-of-vocabulary)
- Универсальное покрытие (эмодзи, любой язык)
- GPT-4 cl100k_base использует этот подход
Токенизация -- скрытый множитель стоимости API
Один и тот же текст на разных языках = разное количество tokens. Английский: ~4 символа/token. Русский: ~1.5-2 символа/token. Китайский: ~1 символ = 2-3 tokens. Это значит: русскоязычный prompt стоит в 2x дороже английского при том же объёме текста. При выборе модели для мультиязычных приложений проверяй tokenizer efficiency на целевом языке.
Сравнение: BPE vs Unigram vs Byte-Level¶
| Aspect | BPE | Unigram | Byte-Level |
|---|---|---|---|
| Vocab building | Greedy merge | Probabilistic prune | Bytes → merge |
| Segmentation | Deterministic | Probabilistic | Deterministic |
| OOV handling | Fallback to chars | Probabilistic | Never (bytes) |
| Multilingual | Medium | Good | Best |
| Speed | Fast | Slower | Fast |
| Used by | GPT, Llama | T5, XLNet | GPT-2+ |
Когда что использовать¶
| Сценарий | Рекомендация |
|---|---|
| Английский текст | BPE или Unigram |
| Мультиязычный | Byte-Level BPE или Unigram |
| Много кода | Byte-Level BPE |
| Морфологически богатый язык | Unigram |
| Production LLM | Byte-Level BPE (стандарт) |
Размер словаря: trade-offs¶
Влияние размера¶
| Vocab Size | Pros | Cons |
|---|---|---|
| 10K | Smaller embeddings | Longer sequences |
| 32K | Balanced | — |
| 100K | Shorter sequences | Larger model |
| 250K+ | Very efficient | Overfitting risk |
Размеры словарей современных LLM¶
| Модель | Размер словаря | Тип |
|---|---|---|
| GPT-2 | 50K | Byte-BPE |
| GPT-4 | 100K | Byte-BPE |
| Llama 2 | 32K | BPE |
| Llama 3 | 128K | BPE |
| Qwen 2 | 152K | BPE |
| Mistral | 32K | BPE |
Формула: память на эмбеддинги¶
Где \(V\) -- размер словаря, \(d\) -- размерность эмбеддинга
Example: Llama 3 (128K vocab, 4096 dim, FP16) $$ 128000 \times 4096 \times 2 = 1.05\text{GB} $$
Мультиязычная токенизация¶
Проблемы¶
| Проблема | Влияние |
|---|---|
| Дисбаланс скриптов | Латиница: 1 токен, остальные: 3-5 токенов |
| Морфология | Агглютинативные языки требуют больше токенов |
| Покрытие словаря | Low-resource языки недопредставлены |
Исследование Frontiers AI (2025)¶
Статья: "Tokenization efficiency of current foundational LLMs"
Выводы:
- Мультиязычные BPE tokenizer'ы с пересекающимися словарями ухудшают качество
- Особенно страдают token-level задачи (POS tagging)
Переосмысление дизайна мультиязычных токенизаторов (OpenReview 2025)¶
Ключевые выводы:
- Балансировать корпус между языками
- Использовать language-aware сэмплирование
- Byte-level fallback для редких скриптов
Лучшие практики для мультиязычности¶
# Corpus balancing
corpus_weights = {
"en": 0.3, # Reduce English dominance
"zh": 0.15,
"es": 0.1,
"other": 0.45 # Distributed across rest
}
# Vocabulary augmentation
for lang in target_languages:
add_common_tokens(lang, count=500)
Дизайн токенизатора¶
Специальные токены¶
| Токен | Назначение |
|---|---|
<|begin_of_text|> |
Начало документа |
<|end_of_text|> |
Конец документа |
<|start_header_id|> |
Заголовок сообщения |
<|eot_id|> |
Конец хода |
[PAD] |
Паддинг |
[CLS] |
Классификация |
[SEP] |
Разделитель |
Regex-паттерны (стиль GPT-4)¶
import regex
pattern = regex.compile(
r"""'(?i:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?+\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]++[\r\n]*|\s*[\r\n]|\s++|\S"""
)
Назначение: предотвратить слияние через границы слов.
Пре-токенизация¶
# Normalization before tokenization
def pre_tokenize(text):
text = normalize_unicode(text) # NFC normalization
text = text.replace("\t", " ")
return text
Теоретико-информационный взгляд (2026)¶
Статья: "An Information-Theoretic Perspective on LLM Tokenizers" (arXiv 2601.09039, Jan 2026)
Ключевые выводы¶
| Домен | Влияние токенизатора |
|---|---|
| Код | Высокое -- структурированный синтаксис |
| Математика | Среднее -- символы важны |
| Английский | Низкое -- многие варианты работают |
| Мультиязычный | Высокое -- различия скриптов |
Эффективность сжатия¶
Целевое значение: 3-5 символов на токен (английский)
Токенизация кода¶
Проблемы¶
- Имена переменных:
getUserByIdvsget_user_by_id - Спецсимволы:
{ } [ ] ( ) < > - Значимость пробелов в Python
Лучшие практики¶
# Code-aware tokenization
code_patterns = [
r"[A-Za-z_][A-Za-z0-9_]*", # Identifiers
r"[0-9]+", # Numbers
r"[+\-*/=<>!&|]+", # Operators
r"[(){}\[\],.;:]", # Punctuation
r"\s+", # Whitespace
]
Расширение словаря¶
# Add common code tokens
code_tokens = [
"def", "class", "import", "return",
"if", "else", "for", "while",
"True", "False", "None",
"function", "const", "let", "var"
]
Vocab size 32K vs 128K: больше != лучше
Llama 2 (32K vocab) -> Llama 3 (128K vocab) улучшил multilingual, но embedding layer вырос с 0.26 GB до 1.05 GB. Для fine-tuning маленьких моделей (<1B) большой vocab -- значительная доля параметров. Для English-only задач 32K достаточно. 128K+ оправдан только для multilingual/code. Embedding memory: \(V \times d \times 2\) bytes (FP16).
Руководство по реализации¶
Обучение токенизатора (HuggingFace)¶
from tokenizers import Tokenizer, models, trainers, pre_tokenizers
# Initialize
tokenizer = Tokenizer(models.BPE())
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel()
# Train
trainer = trainers.BpeTrainer(
vocab_size=32000,
special_tokens=["<|endoftext|>", "<|pad|>"]
)
tokenizer.train(files=["corpus.txt"], trainer=trainer)
# Save
tokenizer.save("tokenizer.json")
Использование SentencePiece¶
import sentencepiece as spm
spm.SentencePieceTrainer.train(
input="corpus.txt",
model_prefix="tokenizer",
vocab_size=32000,
model_type="unigram", # or "bpe"
character_coverage=0.9995
)
Проверка эффективности токенизации¶
def check_efficiency(tokenizer, texts):
total_chars = sum(len(t) for t in texts)
total_tokens = sum(len(tokenizer.encode(t)) for t in texts)
ratio = total_chars / total_tokens
print(f"Chars/Tokens: {ratio:.2f}")
# Target: 3-5 for English, 1-2 for CJK
Богатая морфология¶
Статья: "Rethinking Tokenization for Rich Morphology: The Dominance of Unigram" (arXiv 2508.08424)
Выводы¶
| Тип языка | Лучший алгоритм |
|---|---|
| Аналитический (английский) | BPE |
| Агглютинативный (турецкий, финский) | Unigram |
| Фузионный (русский, немецкий) | Unigram или BPE |
Почему Unigram для морфологии¶
- Вероятностная сегментация уважает границы морфем
- Лучше обработка редких словоформ
- Более консистентная cross-morpheme токенизация
Interview Questions¶
1. Чем BPE отличается от Unigram?¶
Red flag: "BPE мерджит пары, Unigram -- нет"
Strong answer: "BPE строит словарь снизу вверх: начинает с символов, мерджит самую частую пару на каждом шаге (greedy). Unigram -- сверху вниз: начинает с большого словаря, убирает токены с минимальным impact на likelihood. BPE дает детерминированную сегментацию, Unigram -- вероятностную (можно сэмплировать разные разбиения). Для морфологически богатых языков (русский, турецкий) Unigram лучше, потому что уважает границы морфем."
2. Почему Byte-Level BPE стал стандартом?¶
Red flag: "Потому что работает с любым текстом"
Strong answer: "Byte-Level BPE оперирует UTF-8 байтами вместо символов. Три ключевых преимущества: (1) нулевой OOV -- любой текст, эмодзи, любой скрипт представляется через 256 базовых байтов, (2) не нужен огромный базовый алфавит для мультиязычности, (3) единый tokenizer для всех языков и модальностей (текст + код). Недостаток: один символ CJK = 3 байта = потенциально 3 токена. GPT-4 (cl100k_base), LLaMA 3 используют этот подход."
3. Vocab 32K vs 128K: когда что использовать?¶
Red flag: "Больше всегда лучше"
Strong answer: "128K+ оправдан для мультиязычных и code моделей: LLaMA 3 увеличил с 32K до 128K ради лучшего покрытия не-латинских скриптов. Но embedding layer вырос с 0.26 GB до 1.05 GB (FP16). Для English-only задач 32K достаточно. Для маленьких моделей (<1B) большой словарь = значительная доля параметров уходит в embeddings. Формула: \(V \times d \times 2\) байт."
4. Спроектируйте tokenizer для мультиязычного чат-бота¶
Red flag: "Возьмем стандартный BPE"
Strong answer: "1) Byte-Level BPE для нулевого OOV. 2) Балансировка корпуса: не 80% English, а пропорционально целевым языкам. 3) Vocab ~100K+ для покрытия всех скриптов. 4) Augmentation: добавить частые токены целевых языков (500-1000 на язык). 5) Метрика: characters-per-token ratio по языкам, цель -- минимизировать разброс между языками. 6) Тестирование: проверить что 'Привет, как дела?' не стоит в 3x больше токенов чем 'Hello, how are you?'"
Сводка формул¶
Формулы токенизации
BPE Merge Score: $\(\text{Score}(pair) = \text{Frequency}(pair)\)$
Unigram Probability: $\(P(x) = \prod_{t \in \text{tokenize}(x)} p(t)\)$
WordPiece Score: $\(\text{Score}(pair) = \frac{P(pair)}{P(first) \times P(second)}\)$
Compression Ratio: $\(\text{Ratio} = \frac{\text{Total Characters}}{\text{Total Tokens}}\)$
Embedding Memory: $\(\text{Memory} = V \times d \times \text{bytes\_per\_param}\)$
Самопроверка
- Возьмите строку "машинное обучение" и пройдите 3 итерации BPE вручную (начиная с символов). Какие пары будут мерджиться первыми?
- Рассчитайте memory embedding layer для модели с vocab=64K, dim=2048, FP16. Сравните с vocab=128K.
- Текст на русском: "Привет, мир!" занимает 8 токенов в GPT-4 (cl100k_base). Тот же текст на английском "Hello, world!" -- 4 токена. Объясните почему и предложите, как уменьшить разрыв.
Источники и литература¶
Статьи¶
- [Rich Morphology] "Rethinking Tokenization for Rich Morphology" (arXiv 2508.08424, 2025)
- [Info Theory] "An Information-Theoretic Perspective on LLM Tokenizers" (arXiv 2601.09039, 2026)
- [Efficiency] "Tokenization efficiency of foundational LLMs" (Frontiers AI, 2025)
- [Multilingual] "The Art of Breaking Words: Multilingual Tokenizer Design" (OpenReview, 2025)
Библиотеки¶
- HuggingFace Tokenizers: https://github.com/huggingface/tokenizers
- SentencePiece: https://github.com/google/sentencepiece
- tiktoken: https://github.com/openai/tiktoken
Руководства¶
- "Tokenizer Design: Choosing BPE, Unigram, and Vocabulary Size" (Oct 2025)
- "How LLM Tokenization Actually Works Under the Hood" (LetsDataScience)
See Also¶
- BPE Implementation -- пошаговая реализация BPE tokenizer с нуля на Python
- Tokenization Comparison -- сводная таблица BPE vs Unigram vs WordPiece vs Byte-Level
- LLM API Pricing -- tokenizer efficiency напрямую влияет на стоимость API
- Efficient Transformers -- sequence length (определяемая tokenizer) влияет на \(O(n^2)\) attention
- Open-Source LLMs -- vocab sizes и tokenizer choices разных моделей