Глава 6 из 17 30 мин
Токены
Восемь токенов могут вместить больше текста, чем восемь букв. Разберём BPE, байтовый словарь Ростка и то, почему разные языки занимают в нём разное число позиций.
В этой главе
- объяснить, зачем модели токены и почему не буквы и не слова
- прогнать алгоритм BPE вручную и прочитать настоящий токенизатор Ростка
- понимать, во что обходится размер словаря и почему нелатинские языки для маленькой модели дороже
Окно нашей модели вмещает восемь символов. Если текст заканчивается на «the litt», в окне есть короткое слово «the», но следующее слово уже не помещается целиком. Увеличить окно можно, однако это увеличит и первый слой сети. Попробуем другой путь: дадим каждой позиции хранить более крупный фрагмент текста.
Многие языковые модели используют для этого токены переменной длины. Частое слово может стать одним токеном, редкое — несколькими. Эти фрагменты не обязаны совпадать со слогами или морфемами: их выбирает алгоритм токенизации. У Ростка словарь из 8 192 токенов. Посмотрим, как он разбирает знакомые слова и незнакомый ему русский текст.
Как Росток читает
Ниже — настоящий токенизатор Ростка, тот же, с которым он обучался. Каждый цвет — отдельный токен. Напишите что угодно или нажмите на готовые примеры, в том числе на русский текст и эмодзи.
Несколько вещей бросаются в глаза сразу.
- Частые слова — это один токен, причём вместе с пробелом перед ними: «·time», «·little», «·garden». Для модели «·the» в середине фразы и «The» в начале — разные токены.
- Редкое слово собирается из кусков: antidisestablishmentarianism превращается в девять токенов — «·ant | id | is | est | ab | lish | ment | arian | ism». В словаре нет цельного токена для этого слова, поэтому токенизатор использует его части.
- Числа идут по одной цифре. Это не случайность, а правило, о нём ниже.
- Русский текст рассыпается: почти каждая буква — два токена-осколка. В примере «Жили-были дед да баба…» 22 символа и 41 токен. Знак 🌱 разбивается на четыре байтовых токена.
Внизу виджета видно, что мы выиграли. Окно из восьми букв — это обрывок слова. Окно из восьми токенов в английском тексте — это почти целая фраза: на нашем проверочном тексте в среднем 4,085 символа на токен, так что в восемь ячеек помещается около 33 символов. А в русском окно становится даже меньше буквенного: восемь токенов — всего четыре-пять букв. Почему так вышло — главная интрига этой главы.
Буквы — мелко, слова — крупно
Прежде чем изобретать токены, разберёмся с двумя очевидными вариантами.
Буквы. Словарь крошечный — 97 символов для наших очищенных текстов; знаки за пределами этого набора он не покрывает. Но текст получается длинным: сказка в тысячу символов — это тысяча шагов, и на каждом модель решает крошечную задачу «какая буква следующая». Всё, что модель должна помнить, приходится растягивать на огромное число позиций. В главе 7 мы увидим, что число пар позиций в полном внимании растёт как квадрат длины последовательности: при увеличении длины вчетверо таких пар становится в шестнадцать раз больше. Это относится к этой части вычислений, а не ко времени работы модели целиком.
Слова. Текст короткий, каждая ячейка — целое слово. Но словарь разбухает. Только в нашем проверочном тексте — 7,2 миллиона токенов простых историй и диалогов — мы насчитали 28 666 разных слов, и 6 357 из них встречаются ровно один раз. Каждому слову нужна своя строка в таблице эмбеддингов, а редкие строки почти не учатся. Хуже того, любое новое слово — имя, опечатка, «Росток» — оказывается вне словаря, и модели остаётся только метка «неизвестное».
Кусочки слов — золотая середина. Частые слова целиком, редкие — из частей, а совсем незнакомое слово — хоть по буквам. Неизвестных слов не бывает вовсе. На нашем проверочном тексте слово с пробелом перед ним в 95% случаев оказывается ровно одним токеном — почти как в словаре слов, но без его проблем.
В самом низу — байты
С чего начинать нарезку? Можно было бы начать с символов, но их в мире десятки тысяч: буквы всех алфавитов, иероглифы, стрелки, эмодзи. Поэтому токенизатор Ростка, как и токенизаторы GPT-2 и многих современных моделей, начинает с байтов.
Росток переводит текст в UTF-8. В этой кодировке одна кодовая точка Unicode занимает от одного до четырёх байтов, а байт хранит число от 0 до 255. Буквы латиницы без диакритики, цифры и знаки из ASCII занимают один байт; русские буквы — два. Их первый байт D0 или D1 содержит часть номера кодовой точки, второй — оставшуюся часть: отдельной команды «дальше кириллица» здесь нет. Знак 🌱 занимает четыре байта. Но видимый символ может состоять из нескольких кодовых точек — например, эмодзи с оттенком кожи или семейная группа. Тогда байтов будет больше.
Первые 256 токенов Ростка соответствуют всем возможным значениям байта. Поэтому корректный Unicode-текст можно записать даже без знакомых слов: в худшем случае байт за байтом. Ещё 7 932 токена получены последовательными склейками, а четыре номера отведены служебным меткам. Итого $256 + 7\,932 + 4 = 8\,192$.
Склеить самую частую пару
Алгоритм называется BPE, byte pair encoding — «кодирование пар байтов». Он поразительно простой:
- Запишите весь обучающий текст байтами.
- Посчитайте, какая пара соседних токенов встречается чаще всего.
- Объявите эту пару новым токеном и замените все её вхождения.
- Повторяйте, пока словарь не вырастет до нужного размера.
Попробуйте сами на крошечном тексте. Каждое нажатие — одна склейка.
Сначала склеиваются пары, которых больше всего: «a»+«t», «t»+«h», затем «th»+«e» — и вот уже «the» стал одним токеном. Потом к нему прилипает пробел. Слова, которые часто повторяются, быстро становятся целыми токенами, а редкие так и остаются кусками. Никакой лингвистики — только подсчёт.
Вот тот же алгоритм на чистом Python — всего три десятка строк. Запустите его и сравните склейки с виджетом: они совпадут.
Сначала режем на куски
Одна тонкость. Если склеивать пары по всему тексту подряд, BPE охотно выучит токены вроде «g.» или «d the»: в текстах такие сочетания тоже частые. Но они смешивают конец одного слова с началом другого, и словарь тратится впустую. Поэтому перед обучением текст режут на куски регулярным выражением, и склейки никогда не выходят за границы куска. Переключите виджет в начале главы на «Куски», чтобы увидеть эти границы.
Вот это регулярное выражение — прямо из tokenizer.py:
Разберём его по частям; вертикальная черта означает «или»:
'(?:[sdmt]|ll|ve|re)— английские сокращения: 's, 't, 'll, 've, 're. «didn't» режется на «didn» и «'t».?\p{L}+— слово из букв любого алфавита вместе с одним пробелом перед ним. Вот откуда «·little».?\p{N}— одна цифра. Поэтому 2024 — это четыре токена. Так модели легче видеть разряды, и числа не превращаются в тысячи случайных токенов.?[^\s\p{L}\p{N}]+— знаки препинания подряд, например «!"» или «...».\s+(?!\S)|\s+— пробелы и переводы строк.
А вот сам цикл обучения. Он делает ровно то же, что наш мини-BPE, но хитрее считает: помнит, в каких кусках встречается каждая пара, и после склейки пересчитывает только их. Без этого 7 932 склейки на 120 тысячах документов шли бы часами, а так токенизатор Ростка обучился за 19 секунд.
Вывод — настоящий, из обучения Ростка (журнал считает склейки с нуля): самая первая — «·t», к шагу 500 токенизатор уже склеивает целые слова вроде «·flow», а шаг 7 000 даёт «·ambulance».
Как собирается слово
Словарь выучен. Как теперь разрезать новый текст? Кодирование просто повторяет склейки в том порядке, в котором они были выучены: пока в куске есть пара из словаря, склеиваем ту, что выучена раньше всех.
Результат удобно рисовать деревом: внизу байты, над ними склейки, наверху готовый токен. Номер у каждой склейки — это её место в очереди обучения.
Посмотрите на «·the»: склейка № 1 — «·t», № 2 — «he», и уже седьмой склейкой выучен целый «·the». Длинные слова ждут своей очереди долго: «·butterfly» появился только на склейке № 2 095, а «·Congratulations» (16 байтов, один из двух самых длинных токенов словаря; второй — «·recommendations») — только на № 4 312. Любопытно, что «·sprout» со строчной буквы — один токен, а «·Sprout» с заглавной — три: заглавный вариант в историях встречался реже. А «·Росток» — тринадцать байтов (пробел и по два на каждую букву) и ни одной склейки.
Что лежит в словаре
Пройдёмся по словарю от первой склейки до последней. Сдвигайте ползунок: в начале — пары букв и короткие частые слова, дальше — всё более длинные и редкие слова и имена.
Первые склейки — «·t», «he», «·a», «·s», «·w», «in», и седьмая — «·the». В самом конце — «·mushrooms», «Josh», «·armcha» (полкресла: целиком «armchair» не успел стать токеном), «·recommendations». Больше половины словаря — токены вида «пробел и буквы», то есть целые слова или их начала.
Последние четыре номера, 8188–8191, отведены служебным токенам: <|endoftext|>, <|user|>, <|assistant|> и <|end|>. BPE не учит их обычными склейками. Программа либо вставляет номера явно, либо разрешает токенизатору распознавать соответствующие строки. При подготовке обычных текстов распознавание отключено через allow_special=False. В демонстрации оно включено: пример «служебные» показывает каждую метку одной плашкой. Первая отделяет истории, остальные обозначают роли и конец реплики в чате.
Сколько токенов нужно
Почему именно 8 192, а не 1 000 или 100 000? Больше словарь — короче текст, но дороже модель. Ползунок ниже показывает, что было бы с меньшим словарём. Заново обучать токенизатор для этого не нужно: BPE учится жадно, по одной склейке, и если бы мы остановили обучение раньше, получили бы ровно первые склейки нашего словаря.
| Словарь | Символов на токен | Таблица эмбеддингов ×384 |
|---|---|---|
| 256 (только байты) | 1,00 | 98 304 |
| 512 | 2,21 | 196 608 |
| 1 024 | 2,91 | 393 216 |
| 2 048 | 3,50 | 786 432 |
| 4 096 | 3,88 | 1 572 864 |
| 8 192 | 4,09 | 3 145 728 |
Первые 256 склеек укорачивают текст больше чем вдвое. Каждое следующее удвоение словаря даёт всё меньше: переход от 4 096 к 8 192 токенам экономит лишь 5% длины, а таблица эмбеддингов при этом удваивается. У Ростка она и так весит 3,15 миллиона параметров — почти пятую часть модели.
Есть и вторая цена. Чем больше словарь, тем реже встречается каждый токен и тем хуже учится его эмбеддинг. Даже при 8 192 токенах 137 выученных склеек ни разу не встретились в 7,2 миллиона токенов проверочного текста, а 506 — меньше десяти раз. Для модели на 17 миллионов параметров, которая пишет простые английские истории, 8 192 — разумный компромисс.
Как сравнивать модели с разными токенами
Раз уж длина текста зависит от словаря, появляется ловушка. Модель в конце этой главы устроена так же, как в прошлой: тот же класс MLP из snapshots.py, только другого размера — MLP(8192, 8, 48, [384]), восемь токенов по 48 чисел и один скрытый слой на 384 нейрона. На проверочном тексте её удивление — 3,31 ната на токен. У буквенной модели было 1,103 ната на букву. Стало втрое хуже?
Нет: мы сравниваем разные вещи. Угадать следующий токен — значит угадать сразу около четырёх символов, и удивление за них складывается. Честное сравнение — на одну и ту же единицу текста. Если у токена в среднем $c$ символов, то
$$\text{наты на символ} = \frac{\text{наты на токен}}{c} = \frac{3{,}31}{4{,}085} \approx 0{,}81.$$Это заметно лучше, чем 1,103 у буквенной модели. Та же ловушка подстерегает и исследователей: перплексию моделей с разными токенизаторами напрямую сравнивать нельзя, её пересчитывают на символ или на байт.
Закон Ципфа
Токены встречаются очень неравномерно. Лингвист Джордж Ципф заметил это для слов ещё в 1930–1940-х: второе по частоте слово встречается примерно вдвое реже первого, третье — втрое, и так далее. Частота обратно пропорциональна рангу. Если нарисовать это в логарифмическом масштабе по обеим осям, получится почти прямая.
Картина у токенов почти ципфовская: наклон нашей кривой в средней части около −1,2 против −1 у идеала. На практике это значит, что горстка токенов делает почти всю работу. Самый частый токен — точка — это 7,2% текста. Первые десять токенов покрывают 27,9% текста, первые сто — 56,2%, первая тысяча — 86,1%. Остальные семь тысяч делят между собой меньше седьмой части текста. Именно поэтому огромный словарь окупается плохо: каждая новая склейка — всё более редкое слово.
Честно о русском
Теперь понятно, почему русский текст рассыпается на осколки. Токенизатор Ростка учился только на английских историях. Кириллицы в корпусе практически не было — при чистке мы убирали неанглийские тексты, — и ни одна из 7 932 склеек не содержит байта больше 127. Поэтому для русского текста у токенизатора есть только 256 байтовых токенов: каждая буква — два токена, каждый пробел — ещё один.
- «Once upon a time there lived an old man and an old woman. They had a hen.» — 73 символа, 20 токенов: 3,65 символа на токен.
- «Жили-были старик со старухой. Была у них курочка.» — 49 символов, 88 токенов: 0,56 символа на токен.
- «Росток» — 6 букв, 12 токенов.
- 🌱 — один символ, 4 токена.
Одна и та же история по-русски выходит для Ростка вчетверо длиннее. Контекст Ростка — 512 токенов: это около 2 100 символов английского текста, но меньше 300 символов русского — пара абзацев против пары фраз. И это ещё не всё. В проверочном тексте на 7,2 миллиона токенов байты больше 127 встретились всего 30 раз. Эмбеддинги этих токенов почти не обучались: для Ростка русская буква — это два почти пустых вектора подряд.
Эффективность токенизации зависит от того, на каких данных обучали токенизатор, какой у него словарь и как он устроен. Многоязычный BPE может выучить склейки для кириллицы и заметно сократить русские последовательности. Но это отдельная задача от обучения самой языковой модели: возможность компактно записать фразу ещё не означает умения ответить на неё. Соотношение токенов для разных языков нужно измерять у конкретного токенизатора.
Токенизатор — это словарь частых кусочков, выученный подсчётом. Всё, чего в корпусе было много, он читает крупно и дёшево; всё, чего было мало, — мелко и дорого.
Почему Росток тратит на русское слово «кот» шесть токенов, а на английское « cat» — один?
BPE выучил только те пары, которые часто встречались в обучающем тексте. Кириллицы там почти не было, поэтому русские буквы остаются парами байтов, а « cat» склеился в один токен ещё в начале обучения.
А если в тексте встретится что-то совсем странное?
У байтового BPE есть способ закодировать любой корректный Unicode-текст: все исходные байты уже входят в словарь. Например, иероглиф «猫» занимает три байта, 🌱 — четыре, а семейный эмодзи 👨👩👧👦 — 25. У Ростка эти примеры распадаются на отдельные байтовые токены. Токенизатор может собрать исходный текст обратно, но это ничего не говорит о понимании текста моделью. И обратное не гарантируется: при генерации модель может выбрать байты, которые не образуют корректную последовательность UTF-8.
Росток сейчас
Устройство Ростка почти не изменилось — окно из восьми ячеек, эмбеддинги, скрытый слой, — но в каждую ячейку теперь ложится токен, и окно охватывает около 33 символов вместо восьми. Удивление на токен — 3,31 ната на проверочном тексте, или около 0,81 ната на символ против 1,103 у буквенной модели. Параметров стало больше: из 3,7 миллиона около 3,15 миллиона занимает выходной слой, который оценивает каждый из 8 192 токенов. Проза стала связнее, но восемь токенов — это всё ещё одна фраза. Как заглянуть дальше, не раздувая сеть, расскажет следующая глава — о внимании.