Генеративный AI

Токенизация: BPE, SentencePiece

2023 год. Meta выпускает LLaMA 3 и увеличивает словарь с 32K до 128K токенов. Это не маркетинг - это исправление фундаментального бага. Предыдущая версия тратила в 3 раза больше токенов на русский и арабский тексты, чем на английский. Тот же запрос, в 3 раза дороже, в 3 раза медленнее. Токенизация определяет, что модель вообще «видит».

  • **GPT-4 API** тарифицирует по токенам: один и тот же вопрос на русском стоит в 2-3 раза дороже, чем на английском - из-за менее эффективной токенизации кириллицы
  • **BERT** использует WordPiece с 30K словарём и маркером ## для продолжений - именно так модель понимает морфологию: «play» + «##ing» = «playing»
  • **LLaMA 3** увеличил словарь с 32K до 128K именно для лучшей мультиязычной токенизации - и качество на не-английских языках заметно выросло

Рико Сеннрих и адаптация BPE для NLP

В 1994 году Филипп Гейдж придумал BPE как алгоритм сжатия данных - заменять частые пары байт одним байтом. В 2016 году Рико Сеннрих из Эдинбургского университета адаптировал эту идею для нейронного машинного перевода. Проблема была простой: как перевести слово «Bezirksregierungen» (немецкое, 18 символов), которого нет в словаре? BPE разбивал его на известные кусочки. Статья Sennrich et al. набрала 10 000 цитирований за 8 лет - один из самых влиятельных вкладов в современный NLP. Сегодня BPE живёт в каждой LLM.

Предварительные знания

  • Language Models: from n-gram to GPT

Byte Pair Encoding: от символов к субсловам

GPT-2 спросили: сколько будет 1234 + 5678? Ошибся. Не потому что не «умеет» складывать - а потому что число «5678» разбито на токены «56» и «78». Модель видит **два отдельных куска**, а не одно число. Это следствие **токенизации**: процесса, который превращает текст в последовательность чисел (token IDs) ещё *до* того, как нейросеть начинает работать. Невидимый, но критически важный этап.

Почему нельзя просто разбить текст по словам? Три причины: 1. **словарь слишком большой** - в английском ~170,000 слов, а с учётом форм, опечаток, имён - миллионы 2. **OOV (out-of-vocabulary)** - новые слова, аббревиатуры, сленг не попадают в словарь 3. **морфология** - «играю», «играешь», «играет» - три отдельных токена, хотя корень один. Решение: **субсловная токенизация** - разбивать слова на частотные подстроки.

**Byte Pair Encoding (BPE)** - самый популярный алгоритм субсловной токенизации. Изначально алгоритм сжатия данных (Gage, 1994), адаптированный для NLP (Sennrich et al., 2016). Идея проста: начинаем с отдельных символов и **итеративно сливаем** самую частую пару символов в новый токен. Повторяем, пока словарь не достигнет нужного размера.

**Применение обученного BPE к новому тексту.** После обучения есть упорядоченный список слияний. Для токенизации нового слова: 1. разбиваем на символы 2. применяем слияния **в порядке обучения** - каждое слияние заменяет пару, если она встречается. Незнакомые слова разбиваются на известные подстроки. Слово «lowest» -> «low» + «est» - модель *видит* корень и суффикс.

**GPT-2/3/4 используют Byte-level BPE.** Вместо Unicode-символов работают с **байтами** (256 базовых токенов). Это гарантирует, что *любой* текст (включая эмодзи, иероглифы, бинарные данные) может быть токенизирован без OOV-ошибок. Tiktoken - библиотека OpenAI для быстрой BPE-токенизации.

BPE-токенизатор обучен на английском корпусе. Как он обработает новое немецкое слово «Handschuh» (перчатка), которого нет в словаре?

WordPiece: максимизация likelihood

**WordPiece** (Schuster & Nakajima, 2012) - алгоритм, используемый в **BERT**, **DistilBERT** и других моделях Google. По духу похож на BPE, но с важным отличием в критерии слияния: BPE сливает самую **частую** пару, а WordPiece - пару, которая **максимально увеличивает likelihood** обучающего корпуса.

Формально, WordPiece выбирает пару (a, b) для слияния, если слияние максимизирует: **score(a, b) = freq(ab) / (freq(a) * freq(b))**. Это похоже на **pointwise mutual information (PMI)** - меру того, насколько часто два символа встречаются вместе по сравнению с независимостью. Пара «qu» получит высокий score, потому что «q» почти всегда идёт с «u».

**Маркировка продолжений с ##.** WordPiece помечает токены, которые являются *продолжением* слова, префиксом `##`. Это позволяет модели отличать начало слова от его середины. Например: «playing» -> [«play», «##ing»], «unbelievable» -> [«un», «##believ», «##able»]. Без `##` модель не отличила бы «a play» (существительное) от «play##ing» (часть слова).

АспектBPEWordPiece
Критерий слиянияМаксимальная частота парыМаксимальный likelihood (PMI)
МаркировкаНет специального маркера## для продолжений
Используется вGPT-2/3/4, RoBERTa, LLaMABERT, DistilBERT, Electra
Обработка редких словРазбивка на субсловаРазбивка на субслова с ##
Размер словаря32K-100K (GPT: 50,257)30K (BERT: 30,522)

**На практике разница между BPE и WordPiece невелика.** Оба алгоритма производят субсловные токенизации схожего качества. Выбор зависит от экосистемы: если работа с моделями OpenAI/Meta - BPE, с Google - WordPiece. Для новых проектов чаще выбирают BPE или Unigram.

BERT-токенизатор (WordPiece) разбивает слово «playing» на [«play», «##ing»]. Зачем нужен префикс ##?

SentencePiece: language-agnostic токенизация

BPE и WordPiece предполагают, что текст уже **предварительно обработан**: разбит на слова пробелами, очищен от лишних символов, нормализован. Это работает для английского, но для **японского, китайского, тайского** (языков без пробелов) или **немецкого** (длинные составные слова) - нет. **SentencePiece** (Kudo & Richardson, 2018) решает эту проблему: работает с **сырым текстом** напрямую, включая пробелы как часть алфавита.

Ключевая идея: SentencePiece **не требует pre-tokenization**. Пробел обозначается специальным символом `▁` (Unicode U+2581) и становится частью токенов. Текст «I love cats» представляется как «▁I▁love▁cats», и токенизация работает на этой цепочке символов. Это делает алгоритм **language-agnostic** - одинаково хорошо работает с любым языком.

SentencePiece поддерживает два алгоритма: **BPE** (как описано выше) и **Unigram** (Kudo, 2018). Unigram - принципиально другой подход: вместо итеративного *добавления* токенов (BPE), начинает с **большого** словаря и итеративно *удаляет* наименее полезные токены. На каждом шаге Unigram убирает токен, удаление которого **меньше всего увеличивает loss** корпуса.

**Кто использует SentencePiece?** LLaMA (Meta), T5 (Google), ALBERT, XLNet, mBART - все мультиязычные модели. GPT-2/3/4 используют свой byte-level BPE через библиотеку tiktoken, но идея та же: работа с байтами вместо символов делает токенизатор language-agnostic.

**Unigram vs BPE на практике.** Unigram имеет теоретическое преимущество: может давать *несколько* возможных сегментаций с вероятностями, что полезно для data augmentation. Во время обучения модели можно сэмплировать разные токенизации одного текста - это **subword regularization** (Kudo, 2018), улучшает робастность модели.

Почему SentencePiece обозначает пробел специальным символом ▁ и включает его в токены, а не просто разбивает текст по пробелам?

Размер словаря и специальные токены

Один из важнейших гиперпараметров токенизатора - **размер словаря (vocabulary size)**. GPT-2 использует 50,257 токенов, LLaMA - 32,000, GPT-4 - 100,256. Это не случайные числа - за ними стоит фундаментальный **trade-off**, который напрямую влияет на качество модели.

**Как размер словаря влияет на длину последовательности?** Предложение «Tokenization is important for language models» при словаре 1K разбивается на ~15 токенов (много коротких кусков), а при словаре 100K - на ~6 токенов (целые слова). Критично: у Transformer сложность **O(n^2)** от длины последовательности. Вдвое длиннее = вчетверо дороже по памяти и вычислениям.

**Специальные токены** - зарезервированные ID с особым значением для модели. Они не встречаются в обычном тексте, но управляют поведением модели.

ТокенЗначениеГде используется
[CLS]Начало входа, его embedding = представление всего текстаBERT
[SEP]Разделитель между двумя сегментами текстаBERT
[MASK]Замаскированный токен (для предсказания при обучении)BERT
[PAD]Дополнение до одинаковой длины в batchВсе модели
<|endoftext|>Конец документа / начало новогоGPT-2/3
<|im_start|>Начало сообщения (роль: system/user/assistant)ChatGPT
<|im_end|>Конец сообщенияChatGPT
<s>, </s>Начало и конец последовательностиLLaMA, T5

**Мультиязычная дискриминация.** Токенизаторы, обученные преимущественно на английском, расходуют 2-5x больше токенов на тот же текст в других языках. Предложение из 10 слов на английском = ~10 токенов, на русском = ~20, на японском = ~30. Это означает: 1. меньше контекста влезает в окно 2. генерация медленнее 3. **дороже по деньгам** (API тарифицирует по токенам). LLaMA 3 увеличил словарь до 128K именно для лучшей мультиязычной поддержки.

**Byte-fallback** - страховочный механизм. Если токенизатор не может разбить последовательность байтов на известные субслова, он откатывается к **отдельным байтам** (0-255). Это гарантирует, что *любой* вход можно токенизировать - от UTF-8 текста до бинарных данных. GPT-2+ использует byte-level BPE, где базовые 256 токенов - это байты, а всё остальное - слияния поверх них.

**Почему GPT-2 не мог считать?** Число «13579» токенизировалось как «135» + «79» или «1» + «3579» - непредсказуемо. Модель не видела целое число и не могла выполнить арифметику. Современные решения: 1. специальная tokenization для чисел (каждая цифра - отдельный токен) 2. chain-of-thought prompting (разбить вычисление на шаги) 3. tool use (калькулятор).

Ключевые идеи

  • **BPE** - итеративное слияние частых пар символов. Начинает с алфавита, добавляет токены снизу вверх. Используется в GPT-2/3/4, LLaMA. Помните, как «5678» разбивалось на «56» + «78»? Теперь понятно почему
  • **WordPiece** - как BPE, но выбирает пары по likelihood (PMI), а не по частоте. Маркер ## для продолжений слов. Используется в BERT
  • **SentencePiece** - language-agnostic: работает с сырым текстом, пробел ▁ - часть алфавита. Одинаково обрабатывает английский, японский, арабский
  • **Размер словаря** (32K-100K) - критический trade-off: маленький = длинные последовательности, большой = разреженные embeddings. Токенизация определяет, что модель *видит* - и чего она не может

Связанные темы

Токенизация - первый шаг в pipeline любой языковой модели:

  • Языковые модели: от n-gram до GPT — Токенизация определяет единицы, которые модель предсказывает - именно токены, а не слова или символы
  • Transformer-архитектура — Embedding layer преобразует token IDs в векторы - размер словаря определяет размер embedding-матрицы
  • Embeddings и представления — Качество токенизации влияет на качество обученных embeddings - редкие токены получают плохие представления

Вопросы для размышления

  • Если токенизатор обучен на 90% английского текста, модель будет хуже работать с русским. Как бы спроектировать «справедливый» мультиязычный токенизатор? Какие trade-offs возникают?
  • BPE разбивает числа непредсказуемо: «12345» -> «123» + «45» или «1» + «2345». Как решить проблему арифметики в LLM, не меняя архитектуру модели?
  • Unigram-модель может давать *несколько* возможных сегментаций одного текста с разными вероятностями. Как это можно использовать для улучшения обучения модели? (Подсказка: data augmentation)

Связанные уроки

  • gai-02 — Языковые модели предсказывают токены - нужно понимать что именно предсказывается
  • gai-04 — Embedding layer преобразует token IDs в векторы - размер словаря определяет матрицу
  • gai-05 — Качество токенизации влияет на качество обученных embeddings
  • nlp-03 — TF-IDF также работает с частотами слов - разные подходы к представлению текста
  • it-01 — BPE - алгоритм сжатия: информационная теория объясняет почему частые пары сливаются
  • alg-01 — BPE - жадный алгоритм, понимание big-O помогает оценить стоимость обучения
  • fl-05-regex
Токенизация: BPE, SentencePiece

0

1

Войти

Токенизация - это просто техническая деталь, которая не влияет на качество модели. Главное - архитектура и данные

Токенизация НАПРЯМУЮ влияет на способности модели. GPT-2 не мог считать из-за разбиения чисел на непредсказуемые куски. Мультиязычные модели хуже работают с языками, для которых токенизатор расточителен. Размер словаря определяет длину последовательностей и эффективность обучения

Токенизатор - это «глаза» модели: он определяет, как модель *видит* текст. Если число 12345 разбито на [123, 45], модель не может понять, что это одно число. Если русское слово занимает 4 токена вместо 1, контекстное окно вмещает в 4 раза меньше текста. Исследования (Rust et al., 2021) показали корреляцию между fertility (токенов на слово) и downstream performance.

API OpenAI тарифицирует по количеству токенов. Один и тот же вопрос на русском языке стоит в 2-3 раза дороже, чем на английском. Почему?